feat: 扩展rest-bean能力,支持提供表格,表格前端支持状态切换

This commit is contained in:
wangyu 2021-03-24 00:13:06 +08:00
parent 58c20deb65
commit 9dd2563414
12 changed files with 258 additions and 41 deletions

View File

@ -1,6 +1,5 @@
package com.flyfish.framework.compiler.support;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@ -33,6 +32,9 @@ public class JavaSource {
// 查询实体class
private String queryBeanClass;
// 列表视图类
private String listViewClass;
// controller需要提供请求地址
private String uri;
@ -53,4 +55,8 @@ public class JavaSource {
String beanClassName = getBeanClassName();
return queryBeanClass.contains(beanClassName) ? "" : "<" + beanClassName + ">";
}
public String getListViewClassName() {
return StringUtils.substringAfterLast(listViewClass, ".");
}
}

View File

@ -1,10 +1,38 @@
package com.flyfish.framework.utils;
import org.apache.commons.collections4.CollectionUtils;
import java.util.Collection;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Created by wangyu on 2017/9/10.
* 字符串帮助类
*
* @author wangyu
*/
public class StringHelper {
/**
* 获取对象的值
* @param obj 对象
* @return 结果
*/
public static String getObjectValue(Object obj) {
return obj == null ? "" : obj.toString();
}
/**
* 快速的join一个集合
* @param collection 集合
* @param mapper 映射
* @param <T> 泛型
* @return 结果
*/
public static <T> String join(Collection<T> collection, Function<T, String> mapper) {
if (CollectionUtils.isNotEmpty(collection)) {
return collection.stream().map(mapper).collect(Collectors.joining(","));
}
return "";
}
}

View File

@ -0,0 +1,24 @@
package com.flyfish.framework.annotations;
import com.flyfish.framework.enums.NamedEnum;
import java.lang.annotation.*;
/**
* 枚举值标记
*
* @author wangyu
* 可以提供元数据的配置
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface EnumValue {
/**
* 提供明确的类型
*
* @return 结果
*/
Class<? extends NamedEnum> value();
}

View File

@ -0,0 +1,26 @@
package com.flyfish.framework.annotations;
import java.lang.annotation.*;
/**
* 加在类上指定父类的一些属性优先级更高
*
* @author wangyu
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Properties {
/**
* 需要映射的key
* @return 键们
*/
String[] keys();
/**
* 需要映射的标题
* @return 标题们
*/
String[] titles();
}

View File

@ -2,6 +2,11 @@ package com.flyfish.framework.utils;
import com.flyfish.framework.domain.base.Domain;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
/**
* 数据存储使用工具
*
@ -42,6 +47,24 @@ public final class DataUtils {
return null;
}
/**
* 合并对象
* @param keys 键们
* @param names 名称们
* @param mapper 映射
* @param <T> 泛型参数
* @param <R> 泛型结果
* @return 结果
*/
public static <T, R> List<R> zip(String[] keys, T[] names, BiFunction<String, T, R> mapper) {
return IntStream.range(0, Math.min(keys.length, names.length))
.boxed()
.reduce(new ArrayList<>(), (result, index) -> {
result.add(mapper.apply(keys[index], names[index]));
return result;
}, (a, b) -> a);
}
/**
* 创建ref对象
*

View File

@ -1,10 +0,0 @@
package com.flyfish.framework.utils;
/**
* Created by wangyu on 2017/9/10.
*/
public class StringHelper {
public static String getObjectValue(Object obj) {
return obj == null ? "" : obj.toString();
}
}

View File

@ -12,7 +12,7 @@ import lombok.Getter;
@AllArgsConstructor
public enum RestBeanCandidate implements NamedEnum {
REPOSITORY("Repository"), SERVICE("Service"), CONTROLLER("Controller");
REPOSITORY("Repository"), SERVICE("Service"), CONTROLLER("Controller"), VIEW_CONTROLLER("ViewController");
private final String name;
}

View File

@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -28,6 +29,24 @@ public class BeanController {
@Resource
private DynamicRestBeanResolver dynamicRestBeanResolver;
/**
* 通过bean名称获取
*
* @param name 名称
* @return 结果
*/
@GetMapping("{name}")
public Result<BeanInfo> getByName(@PathVariable String name) {
if (StringUtils.isBlank(name)) {
return Result.empty();
}
Class<?> clazz = dynamicRestBeanResolver.getClass(name);
if (null == clazz) {
return Result.empty();
}
return Result.ok(getByClass(clazz));
}
/**
* 通过类标识返回bean信息
*
@ -40,24 +59,30 @@ public class BeanController {
}
try {
Class<?> clazz = ClassUtils.getClass(className);
BeanInfo info = new BeanInfo();
info.setCode(StringFormats.camel2Line(ClassUtils.getShortClassName(clazz)));
if (clazz.isAnnotationPresent(RestBean.class)) {
RestBean annotation = clazz.getAnnotation(RestBean.class);
info.setName(annotation.name());
info.setSearch(BeanProperty.from(annotation.queryClass()));
if (!Vo.class.equals(annotation.listViewClass())) {
info.setColumns(BeanProperty.from(annotation.listViewClass()));
}
}
info.setProperties(BeanProperty.from(clazz));
return Result.ok(info);
return Result.ok(getByClass(clazz));
} catch (ClassNotFoundException e) {
log.error(e.getMessage(), e);
return Result.error(e.getMessage());
}
}
private BeanInfo getByClass(Class<?> clazz) {
BeanInfo info = new BeanInfo();
if (clazz.isAnnotationPresent(RestBean.class)) {
RestBean annotation = clazz.getAnnotation(RestBean.class);
info.setName(annotation.name());
info.setCode(annotation.value());
info.setSearch(BeanProperty.from(annotation.queryClass()));
if (!Vo.class.equals(annotation.listViewClass())) {
info.setColumns(BeanProperty.from(annotation.listViewClass()));
}
} else {
info.setCode(StringFormats.camel2Line(ClassUtils.getShortClassName(clazz)));
}
info.setProperties(BeanProperty.from(clazz));
return info;
}
/**
* 列出自动注册的bean快速接入
*

View File

@ -1,10 +1,16 @@
package com.flyfish.framework.beans.meta;
import com.flyfish.framework.annotations.DictValue;
import com.flyfish.framework.annotations.EnumValue;
import com.flyfish.framework.annotations.Properties;
import com.flyfish.framework.annotations.Property;
import com.flyfish.framework.domain.base.Qo;
import com.flyfish.framework.domain.base.Vo;
import com.flyfish.framework.utils.DataUtils;
import com.flyfish.framework.utils.StringFormats;
import lombok.Data;
import lombok.val;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
@ -41,7 +47,7 @@ public class BeanProperty {
private BeanPropertyType type;
// 只读属性不生成于表单
private boolean readonly;
private transient boolean readonly;
// 属性
private Map<String, Object> props;
@ -60,6 +66,11 @@ public class BeanProperty {
BeanProperty property = new BeanProperty();
property.setName(descriptor.getName());
property.setType(BeanPropertyType.of(descriptor, beanClass));
if (null == property.getType()) {
property.setReadonly(true);
return property;
}
boolean strict = Qo.class.isAssignableFrom(beanClass) || Vo.class.isAssignableFrom(beanClass);
// 尝试获取field
Field field = FieldUtils.getField(beanClass, descriptor.getName(), true);
// 存在field可以干一些坏事
@ -70,15 +81,30 @@ public class BeanProperty {
property.setTitle(props.inherited() ? parentName + props.title() : props.title());
property.setDescription(props.description());
property.setReadonly(props.readonly());
} else if (strict) {
property.setReadonly(true);
return property;
}
} else if (strict) {
property.setReadonly(true);
return property;
}
Class<?> clazz = descriptor.getPropertyType();
switch (property.getType()) {
case STRING:
// 如果字段使用DictValue注解尝试使用字典值仓库
if (null != field && field.isAnnotationPresent(DictValue.class)) {
if (null != field) {
// 添加了字典枚举自动添加code从字典表取得
if (field.isAnnotationPresent(DictValue.class)) {
DictValue dictValue = field.getAnnotation(DictValue.class);
property.prop("code", dictValue.value());
} else if (field.isAnnotationPresent(EnumValue.class)) {
// 添加了枚举注解自动注入类型给前端使用
property.setType(BeanPropertyType.ENUM);
EnumValue enumValue = field.getAnnotation(EnumValue.class);
String name = StringFormats.camel2Line(ClassUtils.getShortClassName(enumValue.value()));
property.prop("code", name);
}
}
case LIST:
// 尝试获取泛型参数存在时赋值子表单
@ -117,11 +143,33 @@ public class BeanProperty {
*/
public static List<BeanProperty> from(Class<?> clazz) {
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz);
return Arrays.stream(descriptors)
List<BeanProperty> properties = Arrays.stream(descriptors)
.filter(descriptor -> !"class".equals(descriptor.getName()))
.map(descriptor -> BeanProperty.form(descriptor, clazz))
.filter(property -> !property.isReadonly())
.collect(Collectors.toList());
return ListUtils.union(parseExtras(clazz), properties);
}
/**
* 解析额外的注解
*
* @param clazz
* @return 结果
*/
private static List<BeanProperty> parseExtras(Class<?> clazz) {
// 包含properties
if (clazz.isAnnotationPresent(Properties.class)) {
Properties properties = clazz.getAnnotation(Properties.class);
return DataUtils.zip(properties.keys(), properties.titles(), (key, name) -> {
BeanProperty property = new BeanProperty();
property.setType(BeanPropertyType.STRING);
property.setName(key);
property.setTitle(name);
return property;
});
}
return Collections.emptyList();
}
/**

View File

@ -9,10 +9,7 @@ import org.springframework.web.reactive.result.method.AbstractHandlerMethodMappi
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* 动态Bean生成解析器
@ -24,7 +21,7 @@ import java.util.Set;
public class DynamicRestBeanResolver implements InitializingBean, DisposableBean {
private static final List<String> controllers = new ArrayList<>();
private static final Set<Class<?>> classes = new HashSet<>();
private static final Map<String, Class<?>> classes = new HashMap<>();
private final Method handlerRegister;
private final RequestMappingHandlerMapping mapping;
@ -37,12 +34,16 @@ public class DynamicRestBeanResolver implements InitializingBean, DisposableBean
controllers.add(controller);
}
public static void setClasses(Set<Class<?>> classSet) {
classes.addAll(classSet);
public static void register(String name, Class<?> clazz) {
classes.put(name, clazz);
}
public Set<Class<?>> getClasses() {
return classes;
public Collection<Class<?>> getClasses() {
return classes.values();
}
public Class<?> getClass(String name) {
return classes.get(name);
}
@Override

View File

@ -8,6 +8,7 @@ import com.flyfish.framework.beans.resolver.DynamicRestBeanResolver;
import com.flyfish.framework.compiler.DynamicJavaCompiler;
import com.flyfish.framework.compiler.support.JavaSource;
import com.flyfish.framework.compiler.template.TemplateCompiler;
import com.flyfish.framework.domain.base.Vo;
import com.flyfish.framework.utils.Assert;
import com.flyfish.framework.utils.StringFormats;
import lombok.extern.slf4j.Slf4j;
@ -47,6 +48,8 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res
.collect(Collectors.toMap(key -> key, key -> TemplateCompiler.compile("/templates/" + key.getName() + ".tpl")));
// 存放父类值的映射
private final Map<RestBeanCandidate, Function<RestBean, Class<?>>> superClasses;
// 默认的父类行为
private final Function<RestBean, Class<?>> noOp = a -> Object.class;
// 编译器引用
private final DynamicJavaCompiler compiler = DynamicJavaCompiler.delegate();
@ -82,20 +85,28 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res
Map<RestBeanCandidate, List<Class<?>>> loadedClasses = new ConcurrentHashMap<>();
// 获取被注解的类开始编译
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(RestBean.class);
DynamicRestBeanResolver.setClasses(classes);
// 并发编译快速接入
classes.parallelStream().forEach(clazz -> {
RestBean restBean = clazz.getDeclaredAnnotation(RestBean.class);
if (null != restBean) {
// 注册bean信息
DynamicRestBeanResolver.register(restBean.value(), clazz);
// 判断是否有vo
boolean hasVo = !Vo.class.equals(restBean.listViewClass());
// 创建java source信息
JavaSource source = JavaSource.create()
.setBeanClass(clazz.getCanonicalName())
.setQueryBeanClass(restBean.queryClass().getCanonicalName())
.setListViewClass(hasVo ? restBean.listViewClass().getCanonicalName() : null)
.setPackageName(basePackages.stream().findFirst().orElse("com.flyfish.project"))
.setUri(makePath(clazz.getSimpleName(), restBean.value()));
// 分别生成实现类从repo到controller
templates.forEach((type, template) -> {
source.setSuperClass(superClasses.get(type).apply(restBean).getCanonicalName());
// 当且仅当存在vo时才编译view-controller
if (type == RestBeanCandidate.VIEW_CONTROLLER && !hasVo) {
return;
}
source.setSuperClass(superClasses.getOrDefault(type, noOp).apply(restBean).getCanonicalName());
source.setClassName(clazz.getSimpleName() + type.getName());
try {
log.info("尝试注册{}", source.getClassName());
@ -115,6 +126,8 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res
.forEach(clazz -> registerBean(clazz, registry));
loadedClasses.getOrDefault(CONTROLLER, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
loadedClasses.getOrDefault(VIEW_CONTROLLER, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
log.info("结束注册rest bean...");
}

View File

@ -0,0 +1,33 @@
package #{packageName};
import #{beanClass};
import #{queryBeanClass};
import #{listViewClass};
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
import com.flyfish.framework.bean.Result;
import com.flyfish.framework.configuration.annotations.PagedQuery;
import com.flyfish.framework.domain.base.Vo;
import com.flyfish.framework.service.BaseService;
@RestController
@RequestMapping("#{uri}")
public class #{className} {
@Autowired
protected BaseService<#{beanClassName}> service;
@GetMapping("views")
public Result<List<Vo<#{beanClassName}>>> getVoList(@PagedQuery #{queryBeanClassName}#{queryBeanSuffix} qo) {
if (null != qo.getPageable()) {
return Result.accept(service.getPageList(qo).map(item -> new #{listViewClassName}().from(item)));
}
return Result.accept(service.getList(qo).stream()
.map(item -> new #{listViewClassName}().from(item)).collect(Collectors.toList()));
}
}