package com.malk.base; import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import lombok.NoArgsConstructor; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import java.beans.PropertyDescriptor; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * 基础对象 * * @JsonInclude(JsonInclude.Include.NON_NULL):类注解过滤null字段,包含入参和返回值【参数类搭配 @Data,才可以实例化返回值以及ToString输出】 * - * lombok * @Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法 * @AllArgsConstructor : 注在类上,提供类的全参构造 * @NoArgsConstructor : 注在类上,提供类的无参构造 * @Setter : 注在属性上,提供 set 方法 * @Getter : 注在属性上,提供 getDefault 方法 * @EqualsAndHashCode : 注在类上,提供对应的 equals 和 hashCode 方法 * @Log4j/@Slf4j : 注在类上,提供对应的 Logger 对象,变量名为 log * @Builder:为类生成相对略微复杂的构建器API。来初始化实例对象::类名.属性(值).属性(值).build() * @Singular:在使用@Singular注释注释一个集合字段(使用@Builder注释类),lombok会将该构建器节点视为一个集合,并生成两个adder方法而不是setter方法::点一次集合增加一个元素 * @Builder.Default:在类中id和insertTime上都添加注解@Builder.Default,当在使用这个实体对象时,就不需要在为这两个字段进行初始化值 */ @Data @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public abstract class BaseDto { /** * 对象拷贝: 若是复制一个对象, 建议使用 cloneParam 避免性能问题 */ @Deprecated @SneakyThrows public BaseDto copyParam() { BaseDto dto = this.getClass().newInstance(); BeanUtils.copyProperties(this, dto); return dto; } /** * * todo: 4.11 继承Serializable后执行深拷贝, 不能类型转换, 不继承Serializable则返回 * 对象拷贝: 复制一个新的对象, 避免条件被修改, 尤其并发下分页混乱情况 */ public BaseDto cloneParam() { return ObjectUtil.clone(this); } /** * 对象属性合并: jda之save接口会以传入数据为准,若传入为空或不传入,更新会置空。目前解决办法两种,通过注解实现JPQL/SQL,或者查询出数据,将未传入字段属性拷贝后再更新【性能消耗】 */ public void mergeParam(BaseDto modifyDo) { BeanUtils.copyProperties(this, modifyDo, getNotNullPropertyNames(modifyDo)); } // 忽略有值的字段 private static String[] getNotNullPropertyNames(Object target) { final BeanWrapper src = new BeanWrapperImpl(target); PropertyDescriptor[] pds = src.getPropertyDescriptors(); Set emptyNames = new HashSet(); for (PropertyDescriptor pd : pds) { Object srcValue = src.getPropertyValue(pd.getName()); if (srcValue != null) { // 此处判断可根据需求修改, 目前过滤不为null emptyNames.add(pd.getName()); } } String[] result = new String[emptyNames.size()]; return emptyNames.toArray(result); } /** * 传入映射的Map, 将实体属性和Map的key转换, 返回Map */ public Map convertEntity(Map reflect) { Map map = JSON.parseObject(JSON.toJSONString(this, SerializerFeature.WriteNullStringAsEmpty), Map.class); Map formData = new HashMap(); for (String key : reflect.keySet()) { String content = String.valueOf(map.get(key)); // json序列化已经将空字符串过滤, 若转换还有null字符串, 可能是key为null或SerializerFeature未指定到类型, 如Date if (StringUtils.isNotBlank(content) && !content.equals("null")) formData.put(reflect.get(key), content); } return formData; } /** * Map时间格式化, 直接从数据库取值后Map会有市区差, 方法1见BasePo, @Temporal & @JsonFormat 注解 * - * [单独时间格式化 [废弃]] * * JSON.parseArray(JSON.toJSONString(data), Map.class).stream().map(item -> { * * item.put("tStoreInTime", UtilDateTime.formatDateTime(new Date(UtilMap.getLong(item, "tStoreInTime")))); * * return item; * * }); */ public static final Object jsonFormatDateTime(Object data) { return JSON.parse(JSON.toJSONString(data, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteDateUseDateFormat)); } }