多维表实践

需求背景

传统业务方案都是通过对业务需求创建关系表,但是针对的是固定业务结构,比如(业务记录ID、创建人、创建时间、更新人、更新时间、业务相关字段等),要想在此进行扩展字段,就需要对相应的业务表尽心添加字段。
但是这些字段都是基于固定的业务进行的添加,无法让用户按照自身需求进行自定义自己的面板数据。
当然,可能对于简单的场景,可以利用mysql 数据可以定义Json/longText 类型的一个字段让用户自定义的字段,放在这个字段上(存放结构好的字段数据),也是可以的。
下面说的一种方案是应用于复杂场景的:
需求
1、对于不同视图看板,可以对不同字段进行设置是否展示(同一条数据);
2、对自定义字段可以应用排序条件进行排序;
3、对所有字段的展示顺序可以拖拽自定义字段顺序;
4、设置不同类型的字段(文本、数字、日期、超链接、图片、人员、单选、多选等);
5、每条记录可以进行拖拽排序;
6、设置每个视图的每个字段的页面展示宽度。
7、数据可以同步到别的数据频道下。
8、字段不能创建重复的(名称、字段属性)
对于这些多变化的需求,固定表结构的方式不适合这种需求场景。

实践方案

对于以上需求可以知道:
1、一条数据有不同的展示方式(可以控制字段是否显示)
2、有多种字段类型,每个字段类型需要不同的处理方式(排序、格式化数据)
3、同一个数据可以同步到别的数据频道下(对于目标数据频道不存在的字段进行新增)

传统固定字段的横向结构不能满足自定义字段的复杂需求。需要把数据打散变成纵向表结构

构建相关表结构:
1、字段表: 字段ID、字段名称、字段类型、字段属性(是否自定义、是否固定、选项[单多选]、格式化样式[数字、日期])
2、视图表:视图ID、视图名称、视图排序位置、视图类型
3、视图-字段关联表:视图ID、字段ID、是否展示、字段顺序
4、元数据表:字段ID、记录ID、字段值(longText)
5、频道-记录表:频道ID、记录ID、记录顺序,原频道ID(记录原数据所属频道)、是否同步关联

伪代码

相关实体类

字段表DAO

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DynamicField extends DynamicBaseEntity {

    /**
     * 主键
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 字段ID
     */
    private String fieldId;

    /**
     * 字段名称
     */
    private String name;

    /**
     * 字段类型
     * 1:多行文本
     * 2:数字
     * 3:单选
     * 4:多选
     * 5:日期
     * 7:复选框
     * 11:人员
     * 15:超链接
     * 17:附件
     * 18:单向关联
     * #19:查找引用
     * #20:公式
     * #21:双向关联
     * 22:评论
     * 1001:创建时间
     * 1002:最后更新时间
     * 1003:创建人
     * 1004:修改人
     * 1005:标记时间
     */
    @Builder.Default
    private int fieldType = 1;

    /**
     * 是否固定字段
     * 固定字段:标题字段,不可移动、修改
     */
    private boolean isFixed;

    /**
     * 是否自定义:用于区分默认字段
     */
    private boolean isCustom;

    /**
     * 字段属性
     */
    @TableField(typeHandler = FieldPropertyHandler.class)
    private FieldProperty property;

    /**
     * 字段校验隔离级别
     * 所属空间ID
     */
    private String spaceId;
}

视图表DAO

@Data
@TableName(autoResultMap = true)
public class DynamicView extends DynamicBaseEntity {

    /**
     * 视图ID
     */
    private String id;

    /**
     * 视图名称
     */
    private String name;

    /**
     * 视图类型
     */
    private ViewTypeEnum viewType;

    /**
     * 所属空间ID
     */
    private String spaceId;

    /**
     * 频道ID
     */
    private String channelId;

    /**
     * 排序
     */
    private Integer indexKey;

    /**
     *  视图页面属性:
     *  
     */
    @TableField(typeHandler = ViewPropertyHandler.class)
    private ViewProperty viewProperty = new ViewProperty();

    /**
     * 排序规则
     */
    @TableField(typeHandler = ListSortHandler.class, javaType = true)
    private List<DynamicSort> sortList;

    /**
     * 筛选规则
     */
    @TableField(typeHandler = ListConditionHandler.class, javaType = true)
    private List<DynamicCondition> conditions;

    /**
     * 符合条件规则
     * 默认:{@link RuleEnum#ALL}
     */
    private RuleEnum rule = RuleEnum.ALL;

    /**
     * 分组规则
     */
    @TableField(typeHandler = ListSortHandler.class, javaType = true)
    private List<DynamicSort> groupList;


    public boolean isNull() {
        if (StringUtils.isEmpty(this.id)) {
            return true;
        }
        return false;
    }
   }

元数据表DAO

@Data
public class MetaDataTable {

    /**
     * 数据ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 数据记录ID
     */
    private String recordId;

    /**
     * 字段ID
     */
    private String fieldId;

    /**
     * 字段值:Json
     */
    private String value;
}

视图-字段关联表DAO

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ViewFieldRelation extends DynamicBaseEntity {

    /**
     * 关联数据ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 字段ID
     */
    private String fieldId;

    /**
     * 所属空间ID
     */
    private String spaceId;

    /**
     * 频道ID
     */
    private String channelId;

    /**
     * 视图ID
     */
    private String viewId;

    /**
     * 是否显示
     */
    @Builder.Default
    private boolean isShow = true;

    /**
     * 字段展示顺序
     */
    private int indexKey;

    /**
     * 字段排版长度
     */
    private Integer fieldLength;

}

记录-频道关联表DAO

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChannelRecordRelation {

    /**
     * 关联数据ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 频道ID
     */
    private String channelId;

    /**
     * 数据记录ID
     */
    private String recordId;

    /**
     * 原数据记录所属频道ID
     */
    private String originId;

    /**
     * 是否是共享数据
     * 默认:false
     */
    private boolean isShare;

    /**
     * 频道数据展示顺序
     */
    private int indexKey;

    /**
     * 字段校验隔离级别
     * 所属空间ID
     */
    private String spaceId;

    @JsonFormat(locale="zh", timezone="GMT+8")
    private Date createdAt;

}

相关类

动态筛选条件类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DynamicCondition {

    /**
     * 筛选字段ID
     */
    private String fieldId;

    /**
     * 匹配规则
     */
    private MatchRuleEnum matchRule;

    /**
     * 匹配值
     */
    private Set<String> matchValue;
}

动态排序条件类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DynamicSort {

    /**
     * 排序字段ID
     */
    private String fieldId;

    /**
     * 排序规则
     */
    private SortEnum sort;

    /**
     * 是否默认固定,不显示
     */
    private boolean isFixed;
}

字段属性类

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FieldProperty {

    /**
     * 字段图标
     */
    private String icon;

    /**
     * 数字格式化格式:泛型
     */
    private NumberTypeEnum formatter;

    /**
     * 日期格式化:泛型
     */
    private DateTypeEnum dateFormatter;

    /**
     * 是否允许添加多成员
     */
    private Boolean addMore;

    /**
     * 单选、多选:选项属性
     */
    private List<Option> options;

}

选项类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Option {

    /**
     * 选项ID
     */
    private String id;

    /**
     * 选项值
     */
    private String value;

    /**
     * 选项颜色:色号
     */
    private String color;

}

超链接类

@Data
public class HyperlinkEntity {

    /**
     * 链接
     */
    private String link;

    /**
     * 链接值
     */
    private String linkValue;
}

视图属性类

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ViewProperty {

    /**
     * 行高:
     * 0-低,默认
     * 1-中等
     * 2-高
     * 3-超高
     */
    private int rowHeight;

    /**
     * 封面:
     *  绑定附件字段ID
     */
    private String cover;

    /**
     * 封面效果:
     *  0-无
     *  1-剪裁;
     *  2-适应
     */
    private int coverEffect;

    /**
     * 展示模式
     *  1-常规
     *  2-紧凑
     */
    @Builder.Default
    private int displayMode = 1;

    /**
     * 是否展示字段名: 默认展示
     */
    private boolean isShowFieldName = true;

    /**
     * 颜色显示: 是否自定义,默认否
     */
    private boolean isCustom;

    /**
     * 颜色:色号
     */
    private String color;

    /**
     * 跟随字段颜色时选定的字段ID及选项
     * 格式:filedId&&id
     */
    private String colorId;

}

相关枚举

日期枚举类

public enum DateTypeEnum {
    /**
     * 年/月/日
     */
    DATE_ONE("yyyy/MM/dd"),

    /**
     * 年/月/日 时:分
     */
    DATE_TWO("yyyy/MM/dd HH:mm"),

    /**
     * 年-月-日
     */
    DATE_THREE("yyyy-MM-dd"),

    /**
     * 年-月-日 时:分
     */
    DATE_FOUR("yyyy-MM-dd HH:mm"),

    /**
     * 月-日
     */
    DATE_FIVE("MM-dd"),

    /**
     * 月/日/年
     */
    DATE_SIX("MM/dd/yyyy"),

    /**
     * 日/月/年
     */
    DATE_SEVEN("dd/MM/yyyy"),

    ;

    private String format;

    DateTypeEnum(String format) {
        this.format = format;
    }

    public String getFormat() {
        return this.format;
    }
}

数字格式化枚举类

public enum NumberTypeEnum {
    /**
     * 整数
     */
    INTEGER("0"),

    /**
     * 保留1位小数
     */
    KEEP_ONE("0.0"),

    /**
     * 保留2位小数
     */
    KEEP_TWO("0.00"),

    /**
     * 保留3位小数
     */
    KEEP_THREE("0.000"),

    /**
     * 保留4位小数
     */
    KEEP_FOUR("0.0000"),

    /**
     * 千分位
     */
    THOUSANDTH("###,###"),

    /**
     * 千分位(保留两位小数点)
     */
    THOUSANDTH_KEEP_TWO("###,###.00"),

    /**
     * 百分比
     */
    PERCENTAGE("###,###%"),

    /**
     * 百分比(保留两位小数点)
     */
    PERCENTAGE_KEEP_TWO("###,###.00%"),

    /**
     * 人民币
     */
    RMB("¥###,###"),

    /**
     * 人民币(保留两位小数点)
     */
    RMB_KEEP_TWO("¥###,###.00"),

    /**
     * 美元
     */
    DOLLAR("$###,###"),

    /**
     * 美元(保留两位小数点)
     */
    DOLLAR_KEEP_TWO("$###,###.00"),

    ;

    private String format;

    NumberTypeEnum(String format) {
        this.format = format;
    }

    public String getFormat() {
        return this.format;
    }
}

筛选匹配枚举类

public enum MatchRuleEnum {
    /**
     * 等于
     * 适用字段类型:单选、多选、文本、人员、日期、数字、复选框、超链接
     */
    EQUAL,

    /**
     * 不等于
     * 适用字段类型:单选、多选、文本、人员、日期、数字、超链接
     */
    NOT_EQUAL,

    /**
     * 大于
     * 适用字段类型:日期、数字
     */
    THAN,

    /**
     * 大于等于
     * 适用字段类型:数字
     */
    THAN_EQUAL,

    /**
     * 小于
     * 适用字段类型:日期、数字
     */
    LESS,

    /**
     * 小于等于
     * 适用字段类型:数字
     */
    LESS_EQUAL,

    /**
     * 包含
     * 适用字段类型:单选、多选、文本、人员、超链接
     */
    CONTAIN,

    /**
     * 不包含
     * 适用字段类型:单选、多选、文本、人员、超链接
     */
    NOT_CONTAIN,

    /**
     * 为空
     * 适用字段类型:单选、多选、文本、人员、日期、附件、数字、超链接
     */
    EMPTY,

    /**
     * 不为空
     * 适用字段类型:单选、多选、文本、人员、日期、附件、数字、超链接
     */
    NOT_EMPTY,

    ;
}

筛选条件应用规则枚举类

public enum RuleEnum {
    /**
     * 所有
     */
    ALL,

    /**
     * 任一
     */
    ANY

    ;
}

排序枚举类

public enum SortEnum {
    /**
     * 正序
     */
    ASC,

    /**
     * 倒序
     */
    DESC

    ;
}

视图类型枚举类

public enum ViewTypeEnum {
    /**
     * 列表视图
     */
    LIST("列表"),

    /**
     * 看板视图
     */
    BOARD("看板"),

    ;

    private String viewName;

    ViewTypeEnum(String viewName) {
        this.viewName = viewName;
    }

    public String getViewName() {
        return this.viewName;
    }
}

自定义排序类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RecordCompareVo implements Comparator<RecordVo> {

    /**
     * 视图
     */
    private List<DynamicSort> sorts;

    @Override
    public int compare(RecordVo o1, RecordVo o2) {
        if (CollectionUtils.isEmpty(sorts)) {
            sorts = new ArrayList<>();
        }
        //默认排序
        DynamicSort indexKey = new DynamicSort();
        indexKey.setFieldId("indexKey");
        indexKey.setSort(SortEnum.ASC);
        sorts.add(indexKey);
        Map<String, MetaDataTableVo> o1Maps = new HashMap<>();
        Map<String, MetaDataTableVo> o2Maps = new HashMap<>();
        List<MetaDataTableVo> o1List = o1.getData();
        List<MetaDataTableVo> o2List = o2.getData();
        if (CollectionUtils.isNotEmpty(o1List)) {
            o1Maps.putAll(o1List.stream().collect(Collectors.toMap(MetaDataTableVo::getFieldId, m -> m)));
        }
        if (CollectionUtils.isNotEmpty(o2List)) {
            o2Maps.putAll(o2List.stream().collect(Collectors.toMap(MetaDataTableVo::getFieldId, m -> m)));
        }
        AtomicInteger cr = new AtomicInteger(0);
        if (CollectionUtils.isNotEmpty(sorts)) {
            sorts.forEach(dynamicSort -> {
                if (Objects.equals(cr.get(), 0)) {
                    if (Objects.equals(dynamicSort.getFieldId(), "indexKey")) {
                        int sort = o1.getIndexKey() - o2.getIndexKey();
                        cr.set(Integer.compare(sort, 0));
                        return;
                    }
                    if (o1Maps.containsKey(dynamicSort.getFieldId()) && o2Maps.containsKey(dynamicSort.getFieldId())) {
                        String o1Value = o1Maps.get(dynamicSort.getFieldId()).getCompareValue();
                        String o2Value = o2Maps.get(dynamicSort.getFieldId()).getCompareValue();
                        if (StringUtils.isEmpty(o1Value) && StringUtils.isEmpty(o2Value)) {
                            cr.set(0);
                        } else if (StringUtils.isEmpty(o1Value)) {
                            cr.set(Objects.equals(dynamicSort.getSort(), SortEnum.ASC) ? -1 : 1);
                        } else if (StringUtils.isEmpty(o2Value)) {
                            cr.set(Objects.equals(dynamicSort.getSort(), SortEnum.ASC) ? 1 : -1);
                        } else {
                            //时间
                            if (Objects.equals(o1Maps.get(dynamicSort.getFieldId()).getFieldType(), 5)) {
                                o1Value = o1Value.replace("-", "");
                                o2Value = o2Value.replace("-", "");
                            }
                            int sortValue = Objects.equals(dynamicSort.getSort(), SortEnum.ASC) ? Collator.getInstance(Locale.CHINA).compare(o1Value, o2Value) : Collator.getInstance(Locale.CHINA).compare(o2Value, o1Value);
                            cr.set(Integer.compare(sortValue, 0));
                        }
                        return;
                    }
                    if (!o1Maps.containsKey(dynamicSort.getFieldId()) && !o2Maps.containsKey(dynamicSort.getFieldId())) {
                        return;
                    }
                    if (!o1Maps.containsKey(dynamicSort.getFieldId())) {
                        cr.set(1);
                        return;
                    }
                    if (!o2Maps.containsKey(dynamicSort.getFieldId())) {
                        cr.set(-1);
                        return;
                    }
                }
            });
        }
        return cr.get();
    }

}

构建记录:筛选排序

public List<RecordVo> buildResults(String spaceId, MetaDataBo bo, DynamicView dynamicView, List<ViewFieldRelationVo> viewFieldRelationVos, List<Map<String, Object>> metaDatas) {

        //返回记录
        List<RecordVo> recordVoList = new ArrayList<>();
       
        //排序条件
        List<DynamicSort> sorts = dynamicView.getSortList();
        Map<String, DynamicSort> sortMaps = new HashMap<>();
        if (CollectionUtils.isNotEmpty(sorts)) {
            sortMaps.putAll(sorts.stream().collect(Collectors.toMap(DynamicSort::getFieldId, s -> s)));
        }
    
       
        if (CollectionUtils.isNotEmpty(metaDatas)) {
            //分组字段数据
            Map<String, List<Map<String, Object>>> map = metaDatas.stream().collect(Collectors.groupingBy(metaData -> (String) metaData.get("recordId")));

            //筛选条件
            List<DynamicCondition> conditions = new ArrayList<>();
            //添加标题筛选
            if (StringUtils.isNotEmpty(bo.getTitle())) {
                DynamicCondition title = new DynamicCondition();
                title.setFieldId(MultiTableConstant.TITLE_FIELD_ID);
                title.setMatchRule(MatchRuleEnum.CONTAIN);
                title.setMatchValue(Set.of(bo.getTitle()));
                conditions.add(title);
            }
            Map<String, List<DynamicCondition>> conditionMaps = new HashMap<>();
            //判断是否符合筛选条件
            if (CollectionUtils.isNotEmpty(dynamicView.getConditions())) {
                conditions.addAll(dynamicView.getConditions());
            }
            if (CollectionUtils.isNotEmpty(conditions)) {
                conditionMaps.putAll(conditions.stream().collect(Collectors.groupingBy(DynamicCondition::getFieldId)));
            }

            map.forEach((recordId, metaDataTables) -> {
                AtomicInteger conditionCount = new AtomicInteger(conditions.size());
                Set<Boolean> conditionSet = new HashSet<>();
                RecordVo recordVo = new RecordVo();
                List<MetaDataTableVo> metaDataTableVos = new ArrayList<>();
                recordVo.setRecordId(recordId);
                AtomicBoolean first = new AtomicBoolean(true);
                //已存在单条数据各个字段数据
                Map<String, Map<String, Object>> fieldMaps = metaDataTables.stream().collect(Collectors.toMap(field -> (String) field.get("fieldId"), m -> m));
                //遍历视图字段
                viewFieldRelationVos.forEach(vf -> {
                    //数据
                    Map<String, Object> m = new HashMap<>();
                    if (Objects.nonNull(fieldMaps.get(vf.getFieldId()))) {
                        m.putAll(fieldMaps.get(vf.getFieldId()));
                    }
                    //获取分组
                    if (Objects.equals(vf.getFieldId(), MultiTableConstant.PHASE_FIELD_ID)) {
                        JSONArray.parseArray((String) m.get("value"), String.class).forEach(phaseId -> {
                            String id = phaseId.split("&&")[0];
                            String channelId = phaseId.split("&&")[1];
                            if (Objects.equals(channelId, dynamicView.getChannelId())) {
                                recordVo.setPhaseId(id);
                            }
                        });
                    }
                    //构建字段
                    MetaDataTableVo value = new MetaDataTableVo();
                    value.setFieldId(vf.getFieldId());
                    value.setFieldLength(vf.getFieldLength());
                    value.setRecordId(recordId);
                    value.setFieldType(vf.getFieldType());
                    //字段类型匹配设置
                    switch (vf.getFieldType()) {
                        //数字
                        case 2:
                            if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) {
                                conditionMaps.get(vf.getFieldId()).forEach(condition -> {
                                    conditionSet.add(checkComparion(condition, StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : StringUtils.EMPTY, 2));
                                    conditionCount.decrementAndGet();
                                });

                            }
                            if (StringUtils.isNotEmpty((String) m.get("value"))) {
                                //数字格式化
                                DecimalFormat df = new DecimalFormat();
                                df.applyPattern(vf.getProperty().getFormatter().getFormat());
                                value.setValue(df.format(Double.valueOf((String) m.get("value"))));
                                value.setCompareValue((String) m.get("value"));
                            }
                            break;
                        //单选
                        case 3:
                            if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) {
                                conditionMaps.get(vf.getFieldId()).forEach(condition -> {
                                    conditionSet.add(checkComparion(condition, Set.of(StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : StringUtils.EMPTY), 3));
                                    conditionCount.decrementAndGet();
                                });
                            }
                            if (StringUtils.isNotEmpty((String) m.get("value"))) {
                                value.setValue(m.get("value"));
                                if (Objects.equals(vf.getFieldId(), MultiTableConstant.FINISH_STATUS)) {
                                    recordVo.setState(Integer.parseInt((String) m.get("value")));
                                }
                                if (Objects.equals(vf.getFieldId(), MultiTableConstant.RECORD_STATE)) {
                                    recordVo.setRecordState(Integer.parseInt((String) m.get("value")));
                                }
                                if (sortMaps.containsKey(vf.getFieldId()) && Objects.nonNull(vf.getProperty()) && Objects.nonNull(vf.getProperty().getOptions())) {
                                    Map<String, Option> options = vf.getProperty().getOptions().stream().collect(Collectors.toMap(Option::getId, o -> o));
                                    Option option = options.get((String) m.get("value"));
                                    value.setCompareValue(Objects.nonNull(option) ? option.getValue() : StringUtils.EMPTY);
                                }
                            }
                            break;
                        //多选
                        case 4:
                            if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) {
                                conditionMaps.get(vf.getFieldId()).forEach(condition -> {
                                    conditionSet.add(checkComparion(condition, Set.copyOf(JSONArray.parseArray(StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : "[]", String.class)), 4));
                                    conditionCount.decrementAndGet();
                                });
                            }
                            if (StringUtils.isNotEmpty((String) m.get("value"))) {
                                List<String> ids = JSONArray.parseArray((String) m.get("value"), String.class);
                                value.setValue(ids);
                                if (sortMaps.containsKey(vf.getFieldId()) && Objects.nonNull(vf.getProperty()) && Objects.nonNull(vf.getProperty().getOptions())) {
                                    Map<String, Option> options = vf.getProperty().getOptions().stream().collect(Collectors.toMap(Option::getId, o -> o));
                                    StringBuilder sb = new StringBuilder();
                                    ids.forEach(id -> {
                                        if (options.containsKey(id)) {
                                            sb.append(options.get(id));
                                        }
                                    });
                                    value.setCompareValue(sb.toString());
                                }
                            }
                            break;
                        //日期
                        case 5:
                            if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) {
                                conditionMaps.get(vf.getFieldId()).forEach(condition -> {
                                    conditionSet.add(checkComparion(condition, Set.of(StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : "0"), 5));
                                    conditionCount.decrementAndGet();
                                });
                            }
                            if (StringUtils.isNotEmpty((String) m.get("value"))) {
                                value.setValue(m.get("value"));
                                value.setCompareValue((String) m.get("value"));
                            } else {
                                value.setValue("0");
                            }
                           
                            break;
                        //人员
                        case 11:
                            if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) {
                                conditionMaps.get(vf.getFieldId()).forEach(condition -> {
                                    conditionSet.add(checkComparion(condition, Set.copyOf(JSONArray.parseArray(StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : "[]")), 11));
                                    conditionCount.decrementAndGet();
                                });
                            }
                            if (StringUtils.isNotEmpty((String) m.get("value"))) {
                                List<UserVO> userVOList = new ArrayList<>();
                                List<String> userIds = JSONArray.parseArray((String) m.get("value"), String.class);
                                StringBuilder sb = new StringBuilder();
                                userIds.forEach(userId -> {
                                    if (userVOMaps.containsKey(userId)) {
                                        userVOList.add(userVOMaps.get(userId));
                                        sb.append(userVOMaps.get(userId).getNickName());
                                    }
                                });
                                value.setValue(userVOList);
                                value.setCompareValue(sb.toString());
                            }
                            break;
                        //超链接
                        case 15:
                            if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) {
                                String linkValue = "";
                                if (StringUtils.isNotEmpty((String) m.get("value"))) {
                                    linkValue = JSONObject.parseObject((String) m.get("value"), HyperlinkEntity.class).getLinkValue();
                                }
                                String finalLinkValue = linkValue;
                                conditionMaps.get(vf.getFieldId()).forEach(condition -> {
                                    conditionSet.add(checkComparion(condition, Set.of(finalLinkValue), 15));
                                    conditionCount.decrementAndGet();
                                });
                            }
                            if (StringUtils.isNotEmpty((String) m.get("value"))) {
                                HyperlinkEntity hyperlink = JSONObject.parseObject((String) m.get("value"), HyperlinkEntity.class);
                                value.setValue(hyperlink);
                                value.setCompareValue(hyperlink.getLinkValue());
                            }
                            break;
                        //附件
                        case 17:
                            if (StringUtils.isNotEmpty((String) m.get("value"))) {
                                if (spaceFileMaps.containsKey((String) m.get("value"))) {
                                    List<DemandFileExtendBO> bos = spaceFileMaps.get((String) m.get("value"));
                                    value.setValue(bos);
                                    StringBuilder sb = new StringBuilder();
                                    if (sortMaps.containsKey(vf.getFieldId()) && CollectionUtils.isNotEmpty(bos)) {
                                        bos.forEach(fileExtendBO -> {
                                            sb.append(fileExtendBO.getName());
                                        });
                                    }
                                    value.setCompareValue(sb.toString());
                                }
                            }
                            if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) {
                                conditionMaps.get(vf.getFieldId()).forEach(condition -> {
                                    conditionSet.add(checkComparion(condition, value.getCompareValue(), 17));
                                    conditionCount.decrementAndGet();
                                });
                            }
                            break;
                        default:
                            value.setCompareValue((String) m.get("value"));
                            value.setValue(m.get("value"));
                            if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) {
                                conditionMaps.get(vf.getFieldId()).forEach(condition -> {
                                    conditionSet.add(checkComparion(condition, Set.of(Optional.ofNullable(m.get("value")).orElse(StringUtils.EMPTY)), 1));
                                    conditionCount.decrementAndGet();
                                });
                            }
                    }
                    metaDataTableVos.add(value);
                });
                if (Objects.equals(dynamicView.getRule(), RuleEnum.ALL) && Objects.equals(conditionCount.get(), 0) && !conditionSet.contains(false)) {
                    recordVo.setData(metaDataTableVos);
                    recordVoList.add(recordVo);
                } else if (Objects.equals(dynamicView.getRule(), RuleEnum.ANY) && conditionCount.get() < conditions.size() && conditionSet.contains(true)) {
                    recordVo.setData(metaDataTableVos);
                    recordVoList.add(recordVo);
                }
            });
            //排序
            Collections.sort(recordVoList, new RecordCompareVo(dynamicView.getSortList()));
        }
        return recordVoList;
    }

筛选处理方法

/**
     * 筛选匹配
     *
     * @param condition
     * @param value
     * @param fieldType
     * @param <T>
     * @return
     */
    private <T> Boolean checkComparion(DynamicCondition condition, T value, int fieldType) {
        Set<String> matchValue = condition.getMatchValue();
        AtomicBoolean result = new AtomicBoolean(true);
        if (Objects.isNull(condition)) {
            return result.get();
        }
        switch (condition.getMatchRule()) {
            //相等
            case EQUAL:
                if ((value instanceof Set) && CollectionUtils.isNotEmpty(matchValue)) {
                    result.set(matchValue.containsAll((Set) value) && matchValue.size() == ((Set) value).size());
                }
                if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) {
                    matchValue.forEach(mv -> {
                        //时间
                        if (Objects.equals(fieldType, 5)) {
                            Long d1 = Math.abs(Long.valueOf(mv));
                            Long d2 = Math.abs(Long.valueOf((String) value));
                            result.set((d2 - d1) == 0);
                        } else if (Objects.equals(fieldType, 2)) {
                            if (StringUtils.isNotEmpty((String) value)) {
                                result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) == 0);
                            } else {
                                result.set(false);
                            }
                        }
                    });
                }
                break;
            //不等于
            case NOT_EQUAL:
                if ((value instanceof Set) && CollectionUtils.isNotEmpty(matchValue)) {
                    result.set(!((Set<?>) value).containsAll(matchValue));
                }
                if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) {
                    matchValue.forEach(mv -> {
                        //时间
                        if (Objects.equals(fieldType, 5)) {
                            Long d1 = Math.abs(Long.valueOf(mv));
                            Long d2 = Math.abs(Long.valueOf((String) value));
                            result.set((d2 - d1) != 0);
                        }
                        //数字
                        if (Objects.equals(fieldType, 2)) {
                            if (StringUtils.isNotEmpty((String) value)) {
                                result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) != 0);
                            } else {
                                result.set(((String) value).compareTo(mv) != 0);
                            }
                        }
                    });
                }
                break;
            //大于
            case THAN:
                if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) {
                    matchValue.forEach(mv -> {
                        //时间
                        if (Objects.equals(fieldType, 5)) {
                            Long d1 = Math.abs(Long.valueOf(mv));
                            Long d2 = Math.abs(Long.valueOf((String) value));
                            result.set((d2 - d1) > 0);
                        }
                        //数字
                        if (Objects.equals(fieldType, 2)) {
                            if (StringUtils.isNotEmpty((String) value)) {
                                result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) > 0);
                            } else {
                                result.set(false);
                            }
                        }
                    });
                }
                break;
            //大于等于
            case THAN_EQUAL:
                if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) {
                    matchValue.forEach(mv -> {
                        if (StringUtils.isNotEmpty((String) value)) {
                            result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) >= 0);
                        } else {
                            result.set(false);
                        }
                    });
                }
                break;
            //小于
            case LESS:
                if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) {
                    matchValue.forEach(mv -> {
                        //时间
                        if (Objects.equals(fieldType, 5)) {
                            Long d1 = Math.abs(Long.valueOf(mv));
                            Long d2 = Math.abs(Long.valueOf((String) value));
                            result.set((d2 - d1) < 0);
                        }
                        //数字
                        if (Objects.equals(fieldType, 2)) {
                            if (StringUtils.isNotEmpty((String) value)) {
                                result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) < 0);
                            } else {
                                result.set(false);
                            }
                        }
                    });
                }
                break;
            //小于等于
            case LESS_EQUAL:
                if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) {
                    matchValue.forEach(mv -> {
                        //数字
                        if (StringUtils.isNotEmpty((String) value)) {
                            result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) <= 0);
                        } else {
                            result.set(false);
                        }
                    });
                }
                break;
            //包含
            case CONTAIN:
                if ((value instanceof Set) && CollectionUtils.isNotEmpty(matchValue)) {
                    AtomicBoolean skip = new AtomicBoolean(false);
                    matchValue.forEach(mv -> {
                        if (!skip.get()) {
                            skip.set(((Set<?>) value).contains(mv));
                        }
                    });
                    result.set(skip.get());
                }
                break;
            //不包含
            case NOT_CONTAIN:
                if ((value instanceof Set) && CollectionUtils.isNotEmpty(matchValue)) {
                    AtomicBoolean skip = new AtomicBoolean(false);
                    matchValue.forEach(mv -> {
                        if (!skip.get()) {
                            skip.set(!((Set<?>) value).contains(mv));
                        }
                    });
                    result.set(skip.get());
                }
                break;
            //为空
            case EMPTY:
                //时间
                if (Objects.equals(fieldType, 5)) {
                    result.set(Long.valueOf((String) value) == 0);
                } else if (Objects.nonNull(value)) {
                    if ((value instanceof String) && StringUtils.isNotEmpty((String) value)) {
                        result.set(false);
                    } else {
                        result.set(false);
                    }
                }
                break;
            //不为空
            case NOT_EMPTY:
                //时间
                if (Objects.equals(fieldType, 5)) {
                    result.set(Long.valueOf((String) value) != 0);
                } else if (Objects.isNull(value)) {
                    result.set(false);
                }
                break;
            default:
        }
        return result.get();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值