Bladeren bron

增加ToString类

zhouhao 6 jaren geleden
bovenliggende
commit
f5cfe11d64

+ 7 - 0
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/GenericEntity.java

@@ -17,6 +17,9 @@
  */
 
 package org.hswebframework.web.commons.entity;
+
+import org.hswebframework.web.bean.ToString;
+
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -35,6 +38,10 @@ public interface GenericEntity<PK> extends CloneableEntity {
 
     void setId(PK id);
 
+    default String toString(String... ignoreProperty) {
+        return ToString.toString(this, ignoreProperty);
+    }
+
     default Map<String, Object> getProperties() {
         return null;
     }

+ 5 - 0
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/SimpleGenericEntity.java

@@ -35,6 +35,11 @@ public abstract class SimpleGenericEntity<PK> implements GenericEntity<PK> {
 
     private Map<String, Object> properties;
 
+    @Override
+    public String toString() {
+        return toString((String[]) null);
+    }
+
     @Override
     public PK getId() {
         return this.id;

+ 1 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/Copier.java

@@ -10,5 +10,6 @@ public interface Copier {
     default void copy(Object source, Object target, String... ignore){
         copy(source,target,new HashSet<>(Arrays.asList(ignore)),FastBeanCopier.DEFAULT_CONVERT);
     }
+
 }
 

+ 342 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/DefaultToStringOperator.java

@@ -0,0 +1,342 @@
+package org.hswebframework.web.bean;
+
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.utils.time.DateFormatter;
+import org.springframework.beans.BeanUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static org.hswebframework.web.bean.ToString.Feature.coverIgnoreProperty;
+import static org.hswebframework.web.bean.ToString.Feature.disableNestProperty;
+import static org.hswebframework.web.bean.ToString.Feature.nullPropertyToEmpty;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+@Slf4j
+public class DefaultToStringOperator<T> implements ToStringOperator<T> {
+
+    private PropertyDescriptor[] descriptors;
+
+    private Set<String> defaultIgnoreProperties;
+
+    private long defaultFeatures = ToString.DEFAULT_FEATURE;
+
+    private Map<String, PropertyDescriptor> descriptorMap;
+
+    private Map<String, BiFunction<Object, ConvertConfig, Object>> converts;
+
+    private Function<Object, String> coverStringConvert = (o) -> coverString(String.valueOf(o), 50);
+
+    private Function<Class, BiFunction<Object, ConvertConfig, Object>> simpleConvertBuilder = type -> {
+        if (Date.class.isAssignableFrom(type)) {
+            return (value, f) -> DateFormatter.toString(((Date) value), "yyyy-MM-dd HH:mm:ss");
+        } else {
+            return (value, f) -> value;
+        }
+    };
+
+    Predicate<Class> simpleTypePredicate = ((Predicate<Class>) String.class::isAssignableFrom)
+            .or(Class::isEnum)
+            .or(Class::isPrimitive)
+            .or(Date.class::isAssignableFrom)
+            .or(Number.class::isAssignableFrom)
+            .or(Boolean.class::isAssignableFrom);
+    private Class<T> targetType;
+
+    public DefaultToStringOperator(Class<T> targetType) {
+        this.targetType = targetType;
+        descriptors = BeanUtils.getPropertyDescriptors(targetType);
+        init();
+    }
+
+    public static String coverString(String str, double percent) {
+        if (str.length() == 1) {
+            return "*";
+        }
+
+        if (percent > 1) {
+            percent = percent / 100d;
+        }
+        percent = 1 - percent;
+        long size = Math.round(str.length() * percent);
+
+        long end = (str.length() - size / 2);
+
+        long start = str.length() - end;
+        start = start == 0 && percent > 0 ? 1 : start;
+        char[] chars = str.toCharArray();
+        for (int i = 0; i < chars.length; i++) {
+            if (i >= start && i <= end - 1) {
+                chars[i] = '*';
+            }
+        }
+        return new String(chars);
+    }
+
+    @SuppressWarnings("all")
+    protected void init() {
+        converts = new HashMap<>();
+        descriptorMap = Arrays.stream(descriptors).collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity()));
+        //获取类上的注解
+        ToString.Ignore classIgnore = AnnotationUtils.getAnnotation(targetType, ToString.Ignore.class);
+        ToString.Features features = AnnotationUtils.getAnnotation(targetType, ToString.Features.class);
+        if (null != features && features.value().length > 0) {
+            defaultFeatures = ToString.Feature.createFeatures(features.value());
+        } else {
+            defaultFeatures = ToString.DEFAULT_FEATURE;
+        }
+        defaultIgnoreProperties = classIgnore == null ?
+                new HashSet<>(Collections.emptySet())
+                : new HashSet<>(Arrays.asList(classIgnore.value()));
+
+        //是否打码
+        boolean defaultCover = classIgnore != null && classIgnore.cover();
+
+        for (PropertyDescriptor descriptor : descriptors) {
+            if ("class".equals(descriptor.getName())) {
+                continue;
+            }
+            Class propertyType = descriptor.getPropertyType();
+            String propertyName = descriptor.getName();
+            BiFunction<Object, ConvertConfig, Object> convert;
+            ToString.Ignore propertyIgnore = null;
+            long propertyFeature = 0;
+            try {
+                Field field = targetType.getDeclaredField(descriptor.getName());
+                propertyIgnore = field.getAnnotation(ToString.Ignore.class);
+                features = AnnotationUtils.getAnnotation(field, ToString.Features.class);
+                if (propertyIgnore != null) {
+                    for (String val : propertyIgnore.value()) {
+                        defaultIgnoreProperties.add(field.getName().concat(".").concat(val));
+                    }
+                }
+                if (null != features && features.value().length > 0) {
+                    propertyFeature = ToString.Feature.createFeatures(features.value());
+                }
+            } catch (NoSuchFieldException e) {
+                log.warn("无法获取字段{},该字段将不会被打码!", descriptor.getName());
+
+            }
+            //是否设置了打码
+            boolean cover = (propertyIgnore == null && defaultCover) || (propertyIgnore != null && propertyIgnore.cover());
+            //是否注解了ignore
+            boolean hide = propertyIgnore != null;
+
+            long finalPropertyFeature = propertyFeature;
+
+            if (simpleTypePredicate.test(propertyType)) {
+                BiFunction<Object, ConvertConfig, Object> simpleConvert = simpleConvertBuilder.apply(propertyType);
+                convert = (value, f) -> {
+                    long feature = finalPropertyFeature == 0 ? f.features : finalPropertyFeature;
+
+                    value = simpleConvert.apply(value, f);
+                    if (hide || f.ignoreProperty.contains(propertyName)) {
+                        if (ToString.Feature.hasFeature(feature, ToString.Feature.coverIgnoreProperty)) {
+                            return coverStringConvert.apply(value);
+                        } else {
+                            return null;
+                        }
+                    }
+                    return value;
+                };
+
+            } else {
+                boolean toStringOverride = false;
+                try {
+                    toStringOverride = propertyType.getMethod("toString").getDeclaringClass() != Object.class;
+                } catch (NoSuchMethodException ignore) {
+                }
+                boolean finalToStringOverride = toStringOverride;
+                boolean justReturn = propertyType.isArray()
+                        || Collection.class.isAssignableFrom(propertyType)
+                        || Map.class.isAssignableFrom(propertyType);
+
+                convert = (value, f) -> {
+                    if (f.ignoreProperty.contains(propertyName)) {
+                        return null;
+                    }
+                    long feature = finalPropertyFeature == 0 ? f.features : finalPropertyFeature;
+
+                    boolean jsonFormat = ToString.Feature.hasFeature(feature, ToString.Feature.jsonFormat);
+                    boolean propertyJsonFormat = ToString.Feature.hasFeature(finalPropertyFeature, ToString.Feature.jsonFormat);
+
+                    if (ToString.Feature.hasFeature(f.features, disableNestProperty)) {
+                        return null;
+                    }
+                    if (!jsonFormat && finalToStringOverride) {
+                        return String.valueOf(value);
+                    }
+
+                    Set<String> newIgnoreProperty = f.ignoreProperty
+                            .stream()
+                            .filter(property -> property.startsWith(propertyName.concat(".")))
+                            .map(property -> property.substring(propertyName.length() + 1))
+                            .collect(Collectors.toSet());
+
+                    if (justReturn) {
+                        if (value instanceof Object[]) {
+                            value = Arrays.asList(((Object[]) value));
+                        }
+                        if (value instanceof Map) {
+                            value = convertMap(((Map) value), feature, newIgnoreProperty);
+                        }
+                        if (value instanceof Collection) {
+                            value = ((Collection) value).stream()
+                                    .map((val) -> {
+                                        if (val instanceof Map) {
+                                            return convertMap(((Map) val), feature, newIgnoreProperty);
+                                        }
+                                        if (simpleTypePredicate.test(val.getClass())) {
+                                            return val;
+                                        }
+                                        ToStringOperator operator = ToString.getOperator(val.getClass());
+                                        if (operator instanceof DefaultToStringOperator) {
+                                            return ((DefaultToStringOperator) operator).toMap(val, feature, newIgnoreProperty);
+                                        }
+                                        return operator.toString(val, feature, newIgnoreProperty);
+                                    }).collect(Collectors.toList());
+
+                        }
+                        if (value instanceof Map) {
+                            value = convertMap(((Map) value), feature, newIgnoreProperty);
+                        }
+                        if (propertyJsonFormat) {
+                            return JSON.toJSONString(value);
+                        }
+                        return value;
+                    }
+
+                    ToStringOperator operator = ToString.getOperator(value.getClass());
+                    if (!propertyJsonFormat && operator instanceof DefaultToStringOperator) {
+                        return ((DefaultToStringOperator) operator).toMap(value, feature, newIgnoreProperty);
+                    } else {
+                        return operator.toString(value, feature, newIgnoreProperty);
+                    }
+                };
+            }
+            converts.put(descriptor.getName(), convert);
+        }
+    }
+
+    class ConvertConfig {
+        long        features;
+        Set<String> ignoreProperty;
+
+    }
+
+    protected Map<String, Object> convertMap(Map<String, Object> obj, long features, Set<String> ignoreProperty) {
+        if (ignoreProperty.isEmpty()) {
+            return obj;
+        }
+        boolean cover = ToString.Feature.hasFeature(features, coverIgnoreProperty);
+        boolean isNullPropertyToEmpty = ToString.Feature.hasFeature(features, nullPropertyToEmpty);
+        boolean isDisableNestProperty = ToString.Feature.hasFeature(features, disableNestProperty);
+
+        Map<String, Object> newMap = new HashMap<>(obj);
+        Set<String> ignore = new HashSet<>(ignoreProperty.size());
+        ignore.addAll(defaultIgnoreProperties);
+
+        for (Map.Entry<String, Object> entry : newMap.entrySet()) {
+            Object value = entry.getValue();
+
+            if (value == null) {
+                if (isNullPropertyToEmpty) {
+                    entry.setValue("");
+                }
+                continue;
+            }
+            Class type = value.getClass();
+            if (simpleTypePredicate.test(type)) {
+                value = simpleConvertBuilder.apply(type).apply(value, null);
+                if (ignoreProperty.contains(entry.getKey())) {
+                    if (cover) {
+                        value = coverStringConvert.apply(value);
+                    } else {
+                        ignore.add(entry.getKey());
+                    }
+                    entry.setValue(value);
+                }
+
+            } else {
+                if (isDisableNestProperty) {
+                    ignore.add(entry.getKey());
+                }
+            }
+        }
+        ignore.forEach(newMap::remove);
+        return newMap;
+    }
+
+    protected Map<String, Object> toMap(T target, long features, Set<String> ignoreProperty) {
+        Map<String, Object> map = target instanceof Map ? ((Map) target) : FastBeanCopier.copy(target, new LinkedHashMap<>());
+
+        Set<String> ignore = ignoreProperty == null || ignoreProperty.isEmpty() ? defaultIgnoreProperties : ignoreProperty;
+        ConvertConfig convertConfig = new ConvertConfig();
+        convertConfig.ignoreProperty = ignore;
+        convertConfig.features = features == -1 ? defaultFeatures : features;
+        Set<String> realIgnore = new HashSet<>();
+
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            Object value = entry.getValue();
+            if (value == null) {
+                if (ToString.Feature.hasFeature(features, ToString.Feature.nullPropertyToEmpty)) {
+                    boolean isSimpleType = false;
+                    PropertyDescriptor propertyDescriptor = descriptorMap.get(entry.getKey());
+                    Class propertyType = null;
+                    if (propertyDescriptor != null) {
+                        propertyType = propertyDescriptor.getPropertyType();
+                        isSimpleType = simpleTypePredicate.test(propertyType);
+                    }
+                    if (isSimpleType || propertyType == null) {
+                        entry.setValue("");
+                    } else if (propertyType.isArray() || Collection.class.isAssignableFrom(propertyType)) {
+                        entry.setValue(Collections.emptyList());
+                    } else {
+                        entry.setValue(Collections.emptyMap());
+                    }
+                }
+                continue;
+            }
+            BiFunction<Object, ConvertConfig, Object> converter = converts.get(entry.getKey());
+            if (null != converter) {
+                entry.setValue(converter.apply(value, convertConfig));
+            }
+            if (entry.getValue() == null) {
+                realIgnore.add(entry.getKey());
+            }
+        }
+        realIgnore.forEach(map::remove);
+
+        return map;
+    }
+
+    @Override
+    public String toString(T target, long features, Set<String> ignoreProperty) {
+        if (target == null) {
+            return "";
+        }
+        if (features == -1) {
+            features = defaultFeatures;
+        }
+
+        Map<String, Object> mapValue = toMap(target, features, ignoreProperty);
+        if (ToString.Feature.hasFeature(features, ToString.Feature.jsonFormat)) {
+            return JSON.toJSONString(mapValue);
+        }
+        StringJoiner joiner = new StringJoiner(", ", target.getClass().getSimpleName() + "(", ")");
+
+        mapValue.forEach((key, value) -> joiner.add(key.concat("=").concat(String.valueOf(value))));
+
+        return joiner.toString();
+    }
+}

+ 134 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/ToString.java

@@ -0,0 +1,134 @@
+package org.hswebframework.web.bean;
+
+import org.springframework.util.ClassUtils;
+
+import java.lang.annotation.*;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public class ToString {
+
+    public static long DEFAULT_FEATURE = Feature.createFeatures(
+            Feature.coverIgnoreProperty
+            , Feature.nullPropertyToEmpty
+//            , Feature.jsonFormat
+    );
+
+    public static final Map<Class, ToStringOperator> cache = new ConcurrentHashMap<>();
+
+    @SuppressWarnings("all")
+    public static <T> ToStringOperator<T> getOperator(Class<T> type) {
+        return cache.computeIfAbsent(type, DefaultToStringOperator::new);
+    }
+
+    @SuppressWarnings("all")
+    public static <T> String toString(T target) {
+        return getOperator((Class<T>) ClassUtils.getUserClass(target)).toString(target);
+    }
+
+    @SuppressWarnings("all")
+    public static <T> String toString(T target, String... ignoreProperty) {
+        return getOperator((Class<T>) ClassUtils.getUserClass(target)).toString(target, ignoreProperty);
+    }
+
+    @Target({ElementType.TYPE, ElementType.FIELD})
+    @Retention(RetentionPolicy.RUNTIME)
+    @Documented
+    public @interface Ignore {
+
+        String[] value() default {};
+
+        boolean cover() default true;
+
+    }
+
+    @Target({ElementType.TYPE, ElementType.FIELD})
+    @Retention(RetentionPolicy.RUNTIME)
+    @Documented
+    public @interface Features {
+        Feature[] value() default {};
+    }
+
+    public enum Feature {
+
+        /**
+         * 什么也不配置
+         *
+         * @since 3.0.0-RC
+         */
+        empty,
+
+        /**
+         * 忽略为null的字段
+         *
+         * @since 3.0.0-RC
+         */
+        ignoreNullProperty,
+
+        /**
+         * null的字段转为空,如null字符串转为"", null的list转为[]
+         *
+         * @since 3.0.0-RC
+         */
+        nullPropertyToEmpty,
+
+        /**
+         * 排除的字段使用*进行遮盖,如: 张三 =? 张* , 18502314087 => 185****087
+         *
+         * @since 3.0.0-RC
+         */
+        coverIgnoreProperty,
+
+        /**
+         * 是否关闭嵌套属性toString
+         *
+         * @since 3.0.0-RC
+         */
+        disableNestProperty,
+
+        /**
+         * 以json方式进行格式化
+         *
+         * @since 3.0.0-RC
+         */
+        jsonFormat;
+
+
+        public long getMask() {
+            return 1L << ordinal();
+        }
+
+        public static boolean hasFeature(long features, Feature feature) {
+            long mast = feature.getMask();
+            return (features & mast) == mast;
+        }
+
+        public static long removeFeatures(long oldFeature, Feature... features) {
+            if (features == null) {
+                return 0L;
+            }
+            long value = oldFeature;
+            for (Feature feature : features) {
+                value &= ~feature.getMask();
+            }
+            return value;
+        }
+
+        public static long createFeatures(Feature... features) {
+            if (features == null) {
+                return 0L;
+            }
+            long value = 0L;
+            for (Feature feature : features) {
+                value |= feature.getMask();
+            }
+
+            return value;
+        }
+    }
+
+}

+ 17 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/ToStringOperator.java

@@ -0,0 +1,17 @@
+package org.hswebframework.web.bean;
+
+
+import java.util.*;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public interface ToStringOperator<T> {
+
+    default String toString(T target, String... ignoreProperty) {
+        return toString(target, -1, ignoreProperty == null ? Collections.emptySet() : new HashSet<>(Arrays.asList(ignoreProperty)));
+    }
+
+    String toString(T target, long features, Set<String> ignoreProperty);
+}

+ 4 - 3
hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java

@@ -26,6 +26,7 @@ public class FastBeanCopierTest {
         source.setNestObject2(Collections.singletonMap("name","mapTest"));
         NestObject nestObject = new NestObject();
         nestObject.setAge(10);
+        nestObject.setPassword("1234567");
         nestObject.setName("测试2");
         source.setNestObject(nestObject);
         source.setNestObject3(nestObject);
@@ -35,9 +36,9 @@ public class FastBeanCopierTest {
 
 
         long t = System.currentTimeMillis();
-        for (int i = 10_0000; i > 0; i--) {
-            FastBeanCopier.copy(source, target);
-        }
+//        for (int i = 10_0000; i > 0; i--) {
+//            FastBeanCopier.copy(source, target);
+//        }
         System.out.println(System.currentTimeMillis() - t);
 
         System.out.println(source);

+ 12 - 6
hsweb-core/src/test/java/org/hswebframework/web/bean/NestObject.java

@@ -1,15 +1,13 @@
 package org.hswebframework.web.bean;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 /**
  * @author zhouhao
  * @since
  */
-@Data
+@Getter
+@Setter
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
@@ -19,8 +17,16 @@ public class NestObject implements Cloneable {
     private int age;
 
 
+    @ToString.Ignore
+    private String password ;
+
     @Override
     public NestObject clone() throws CloneNotSupportedException {
-        return (NestObject)super.clone();
+        return (NestObject) super.clone();
+    }
+
+    @Override
+    public String toString() {
+        return ToString.toString(this);
     }
 }

+ 2 - 2
hsweb-core/src/test/java/org/hswebframework/web/bean/Source.java

@@ -31,11 +31,11 @@ public class Source {
 
     private NestObject nestObject;
 
-    private List<NestObject> nestObjects = Arrays.asList(new NestObject("test", 1),new NestObject("test", 1));
+    private List<NestObject> nestObjects = Arrays.asList(new NestObject("test", 1, "1234567"), new NestObject("test", 1, "1234567"));
 
     private Map<String, Object> nestObject2 = new HashMap<>();
 
-    private NestObject nestObject3;
+    private NestObject nestObject3 = new NestObject("test", 1, "1234567");
 
     private Color color = Color.RED;
 

+ 20 - 6
hsweb-core/src/test/java/org/hswebframework/web/bean/Target.java

@@ -1,6 +1,8 @@
 package org.hswebframework.web.bean;
 
 import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.ArrayList;
@@ -8,15 +10,20 @@ import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
-@Data
+import static org.hswebframework.web.bean.ToString.Feature.coverIgnoreProperty;
+import static org.hswebframework.web.bean.ToString.Feature.jsonFormat;
+import static org.hswebframework.web.bean.ToString.Feature.nullPropertyToEmpty;
+
+@Getter
+@Setter
 public class Target {
-    private String name;
+    private String   name;
     private String[] ids;
 
 
     private Boolean boy;
     private boolean boy2;
-    private String boy3;
+    private String  boy3;
 
     private int age;
 
@@ -24,21 +31,24 @@ public class Target {
 
     private String age3;
 
-    private Date deleteTime=new Date();
+    private Date deleteTime = new Date();
 
     private String createTime;
 
     private Date updateTime;
 
+    @ToString.Features({coverIgnoreProperty,jsonFormat})
+    @ToString.Ignore(value = "password")
     private NestObject nestObject;
 
     private NestObject nestObject2;
 
-    private List<Map<String,Object>> nestObjects;
+    @ToString.Ignore(value = "password")
+    private List<Map<String, Object>> nestObjects;
 
+    @ToString.Ignore("password")
     private Map<String, Object> nestObject3;
 
-
     private int color;
 
     private Color color2;
@@ -55,4 +65,8 @@ public class Target {
     private Integer[] arr4;
 
 
+    @Override
+    public String toString() {
+        return ToString.toString(this);
+    }
 }

+ 0 - 1
hsweb-starter/hsweb-spring-boot-starter/src/main/java/org/hswebframework/web/starter/HswebAutoConfiguration.java

@@ -115,7 +115,6 @@ public class HswebAutoConfiguration {
                 if (type instanceof Class) {
                     Class classType = ((Class) type);
                     if (classType.isEnum()) {
-                        // TODO: 2018/4/12 支持EnumDict枚举的反序列化
                         return super.getDeserializer(type);
                     }
                     checkAutoType(type.getTypeName(), ((Class) type));

+ 3 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-api/src/main/java/org/hswebframework/web/entity/authorization/SimpleUserEntity.java

@@ -3,6 +3,7 @@ package org.hswebframework.web.entity.authorization;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
+import org.hswebframework.web.bean.ToString;
 import org.hswebframework.web.commons.entity.SimpleGenericEntity;
 
 /**
@@ -18,8 +19,10 @@ public class SimpleUserEntity extends SimpleGenericEntity<String> implements Use
 
     private String username;
 
+    @ToString.Ignore
     private String password;
 
+    @ToString.Ignore(cover = false)
     private String salt;
 
     private Long createTime;