feat: 扩展rest-bean能力,支持提供表格,表格前端支持状态切换
This commit is contained in:
parent
58c20deb65
commit
9dd2563414
@ -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, ".");
|
||||
}
|
||||
}
|
||||
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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对象
|
||||
*
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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,快速接入
|
||||
*
|
||||
|
@ -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)) {
|
||||
DictValue dictValue = field.getAnnotation(DictValue.class);
|
||||
property.prop("code", dictValue.value());
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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...");
|
||||
}
|
||||
|
||||
|
33
flyfish-web/src/main/resources/templates/ViewController.tpl
Normal file
33
flyfish-web/src/main/resources/templates/ViewController.tpl
Normal 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()));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user