diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/JavaSource.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/JavaSource.java index da7a636..ee25333 100644 --- a/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/JavaSource.java +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/JavaSource.java @@ -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, "."); + } } diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/StringHelper.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/StringHelper.java index 59a58ab..ef937c1 100644 --- a/flyfish-common/src/main/java/com/flyfish/framework/utils/StringHelper.java +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/StringHelper.java @@ -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 泛型 + * @return 结果 + */ + public static String join(Collection collection, Function mapper) { + if (CollectionUtils.isNotEmpty(collection)) { + return collection.stream().map(mapper).collect(Collectors.joining(",")); + } + return ""; + } } diff --git a/flyfish-data/src/main/java/com/flyfish/framework/annotations/EnumValue.java b/flyfish-data/src/main/java/com/flyfish/framework/annotations/EnumValue.java new file mode 100644 index 0000000..85b01a6 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/annotations/EnumValue.java @@ -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 value(); +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/annotations/Properties.java b/flyfish-data/src/main/java/com/flyfish/framework/annotations/Properties.java new file mode 100644 index 0000000..485dbe9 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/annotations/Properties.java @@ -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(); +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/DataUtils.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/DataUtils.java index 13bc69a..9898159 100644 --- a/flyfish-data/src/main/java/com/flyfish/framework/utils/DataUtils.java +++ b/flyfish-data/src/main/java/com/flyfish/framework/utils/DataUtils.java @@ -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 泛型参数 + * @param 泛型结果 + * @return 结果 + */ + public static List zip(String[] keys, T[] names, BiFunction 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对象 * diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/StringHelper.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/StringHelper.java deleted file mode 100644 index 59a58ab..0000000 --- a/flyfish-data/src/main/java/com/flyfish/framework/utils/StringHelper.java +++ /dev/null @@ -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(); - } -} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/enums/RestBeanCandidate.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/enums/RestBeanCandidate.java index e550ab9..0260bb7 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/beans/enums/RestBeanCandidate.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/enums/RestBeanCandidate.java @@ -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; } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/meta/BeanController.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/meta/BeanController.java index 7d08906..9fc6a0c 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/beans/meta/BeanController.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/meta/BeanController.java @@ -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 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,快速接入 * diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/meta/BeanProperty.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/meta/BeanProperty.java index 55b30c1..0b055e1 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/beans/meta/BeanProperty.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/meta/BeanProperty.java @@ -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 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 from(Class clazz) { PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz); - return Arrays.stream(descriptors) + List 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 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(); } /** diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java index 836c3ac..174bfda 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java @@ -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 controllers = new ArrayList<>(); - private static final Set> classes = new HashSet<>(); + private static final Map> 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> classSet) { - classes.addAll(classSet); + public static void register(String name, Class clazz) { + classes.put(name, clazz); } - public Set> getClasses() { - return classes; + public Collection> getClasses() { + return classes.values(); + } + + public Class getClass(String name) { + return classes.get(name); } @Override diff --git a/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java b/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java index a99cda9..f91ca82 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java @@ -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>> superClasses; + // 默认的父类行为 + private final Function> noOp = a -> Object.class; // 编译器引用 private final DynamicJavaCompiler compiler = DynamicJavaCompiler.delegate(); @@ -82,20 +85,28 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res Map>> loadedClasses = new ConcurrentHashMap<>(); // 获取被注解的类,开始编译 Set> 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..."); } diff --git a/flyfish-web/src/main/resources/templates/ViewController.tpl b/flyfish-web/src/main/resources/templates/ViewController.tpl new file mode 100644 index 0000000..027dffd --- /dev/null +++ b/flyfish-web/src/main/resources/templates/ViewController.tpl @@ -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>> 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())); + } + +}