feat(查询): 实现通用过滤条件构建与解析功能
新增参数常量定义和查询条件构建工具,支持复杂条件组合 重构EntityService基类以支持通用过滤条件解析 优化SpecificationUtils工具类,增加搜索文本处理方法
This commit is contained in:
@@ -4,7 +4,8 @@ import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.constant.ParamConstant;
|
||||
|
||||
@@ -16,64 +17,65 @@ public class ParamUtils {
|
||||
/**
|
||||
* 创建日期范围查询参数
|
||||
*
|
||||
* @param key 查询字段名
|
||||
* @param field 查询字段名
|
||||
* @param begin 开始日期
|
||||
* @param end 结束日期
|
||||
* @return 包含日期范围的查询参数Map
|
||||
*/
|
||||
public static Map<String, Object> between(String key, LocalDate begin, LocalDate end) {
|
||||
return Map.of(key, Map.of(
|
||||
"begin", begin,
|
||||
"end", end));
|
||||
public static Map<String, Object> between(String field, LocalDate begin, LocalDate end) {
|
||||
Builder builder = builder();
|
||||
builder.between(field, begin, end);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建等于条件查询参数
|
||||
*
|
||||
* @param key 查询字段名
|
||||
* @param field 查询字段名
|
||||
* @param value 查询值
|
||||
* @return 包含等于条件的查询参数Map
|
||||
*/
|
||||
public static Map<String, Object> equal(String key, Object value) {
|
||||
return Map.of(key, value);
|
||||
public static Map<String, Object> equal(String field, Object value) {
|
||||
Builder builder = builder();
|
||||
builder.equals(field, value);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建字符串模糊查询参数
|
||||
*
|
||||
* @param key 查询字段名
|
||||
* @param field 查询字段名
|
||||
* @param value 模糊查询的字符串值
|
||||
* @return 包含模糊查询条件的查询参数Map
|
||||
*/
|
||||
public static Map<String, Object> like(String key, String value) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put(key, "%" + value + "%");
|
||||
return params;
|
||||
public static Map<String, Object> contains(String field, String value, boolean caseSensitive) {
|
||||
Builder builder = builder();
|
||||
builder.and().like(field, value, ParamConstant.Mode.CONTAINS, caseSensitive, false);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建日期模糊查询参数
|
||||
*
|
||||
* @param key 查询字段名
|
||||
* @param value 模糊查询的日期值
|
||||
* @return 包含日期模糊查询条件的查询参数Map
|
||||
*/
|
||||
public static Map<String, Object> like(String key, LocalDate value) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put(key, "%" + value + "%");
|
||||
return params;
|
||||
public static Map<String, Object> startsWith(String field, String value, boolean caseSensitive) {
|
||||
Builder builder = builder();
|
||||
builder.and().like(field, value, ParamConstant.Mode.STARTS_WITH, caseSensitive, false);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static Map<String, Object> endsWith(String field, String value, boolean caseSensitive) {
|
||||
Builder builder = builder();
|
||||
builder.and().like(field, value, ParamConstant.Mode.ENDS_WITH, caseSensitive, false);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建非空条件查询参数
|
||||
*
|
||||
* @param key 查询字段名
|
||||
* @param field 查询字段名
|
||||
* @return 包含非空条件的查询参数Map
|
||||
*/
|
||||
public static Map<String, Object> isNotNull(String key) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put(key, Map.of("isNotNull", true));
|
||||
return params;
|
||||
public static Map<String, Object> isNotNull(String field) {
|
||||
Builder builder = builder();
|
||||
builder.isNotNull(field);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,9 +86,22 @@ public class ParamUtils {
|
||||
* @return 包含小于条件的查询参数Map
|
||||
*/
|
||||
public static Map<String, Object> lessThan(String key, LocalDate value) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put(key, Map.of("lessThan", value));
|
||||
return params;
|
||||
Builder builder = builder();
|
||||
builder.lessThan(key, value);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建大于条件的日期查询参数
|
||||
*
|
||||
* @param key 查询字段名
|
||||
* @param value 比较的日期值
|
||||
* @return 包含大于条件的查询参数Map
|
||||
*/
|
||||
public static Map<String, Object> greaterThan(String key, LocalDate value) {
|
||||
Builder builder = builder();
|
||||
builder.greaterThan(key, value);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +120,7 @@ public class ParamUtils {
|
||||
public static class Builder {
|
||||
// 存储构建的查询参数
|
||||
private String searchText;
|
||||
private Map<String, Object> params = new HashMap<>();
|
||||
private FilterGroup group;
|
||||
|
||||
/**
|
||||
* 私有构造方法,防止外部直接实例化
|
||||
@@ -114,18 +129,6 @@ public class ParamUtils {
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
private void addParam(String field, String action, Object value) {
|
||||
Object map = params.computeIfAbsent(field, k -> new HashMap<>());
|
||||
if (map instanceof Map) {
|
||||
((Map) map).put(action, value);
|
||||
return;
|
||||
}
|
||||
HashMap<String, Object> m = new HashMap<>();
|
||||
m.put(action, value);
|
||||
m.put(ParamConstant.KEY_equal, map);
|
||||
params.put(field, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加非空条件到构建器
|
||||
*
|
||||
@@ -133,7 +136,7 @@ public class ParamUtils {
|
||||
* @return 当前Builder实例,支持链式调用
|
||||
*/
|
||||
public Builder isNotNull(String key) {
|
||||
addParam(key, ParamConstant.KEY_isNotNull, true);
|
||||
getDefaultGroup().isNotNull(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -145,7 +148,7 @@ public class ParamUtils {
|
||||
* @return 当前Builder实例,支持链式调用
|
||||
*/
|
||||
public Builder lessThan(String key, LocalDate value) {
|
||||
addParam(key, ParamConstant.KEY_lessThan, value);
|
||||
getDefaultGroup().lessThan(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -157,7 +160,7 @@ public class ParamUtils {
|
||||
* @return 当前Builder实例,支持链式调用
|
||||
*/
|
||||
public Builder greaterThan(String key, LocalDate value) {
|
||||
addParam(key, ParamConstant.KEY_greaterThan, value);
|
||||
getDefaultGroup().greaterThan(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -169,7 +172,7 @@ public class ParamUtils {
|
||||
* @return 当前Builder实例,支持链式调用
|
||||
*/
|
||||
public Builder equals(String key, Object value) {
|
||||
addParam(key, ParamConstant.KEY_equal, value);
|
||||
getDefaultGroup().equals(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -182,10 +185,16 @@ public class ParamUtils {
|
||||
* @return 当前Builder实例,支持链式调用
|
||||
*/
|
||||
public Builder between(String key, LocalDate begin, LocalDate end) {
|
||||
addParam(key, ParamConstant.KEY_between, new Object[]{begin, end});
|
||||
getDefaultGroup().between(key, begin, end);
|
||||
return this;
|
||||
}
|
||||
|
||||
private FilterGroup getDefaultGroup() {
|
||||
if (group != null) {
|
||||
return group;
|
||||
}
|
||||
return and();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加全文搜索条件到构建器
|
||||
@@ -200,56 +209,337 @@ public class ParamUtils {
|
||||
|
||||
/**
|
||||
* 构建并返回查询参数Map
|
||||
* 推荐的结构(仅为契约示例,服务端需配套解析):
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* searchText: "搜索文本",
|
||||
* and: [
|
||||
* {field: "字段名", action: "equal", value: "值"},
|
||||
* {field: "字段名", action: "between", value: [begin, end]}
|
||||
* {field: "字段名", action: "or", value: [
|
||||
* {field: "字段名", action: "equal", value: "值"},
|
||||
* ]}
|
||||
* ],
|
||||
* or: [
|
||||
* {field: "字段名", action: "操作符", value: "值"}
|
||||
* ]
|
||||
* "searchText": "关键词",
|
||||
* "filter": {
|
||||
* "op": "and",
|
||||
* "conditions": [
|
||||
* { "field": "code", "op": "equal", "value": "V-0001" },
|
||||
* {
|
||||
* "field": "createdDate",
|
||||
* "op": "between",
|
||||
* "value": {
|
||||
* "begin": "2024-01-01",
|
||||
* "end": "2024-12-31",
|
||||
* "includeBegin": true,
|
||||
* "includeEnd": false
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* "field": "name",
|
||||
* "op": "like",
|
||||
* "value": "元素",
|
||||
* "mode": "contains",
|
||||
* "caseSensitive": false
|
||||
* },
|
||||
* {
|
||||
* "op": "or",
|
||||
* "conditions": [
|
||||
* { "field": "name", "op": "like", "value": "元素", "mode": "contains", "caseSensitive": false },
|
||||
* { "field": "abbName", "op": "like", "value": "元素", "mode": "contains", "caseSensitive": false }
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* 约定说明:
|
||||
* - 分组节点:{ op: "and"|"or", conditions: [...] }
|
||||
* - 叶子条件:{ field: "路径", op: "equal"|"between"|..., value: ... }
|
||||
* - like 扩展:支持 mode("contains"|"startsWith"|"endsWith") 和
|
||||
* caseSensitive(boolean)
|
||||
* - between 扩展:value 为对象,支持包含边界 includeBegin/includeEnd
|
||||
*
|
||||
* @return 包含所有添加条件的查询参数Map
|
||||
*/
|
||||
public Map<String, Object> build() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put(ParamConstant.KEY_SEARCH_TEXT, searchText);
|
||||
if (StringUtils.hasText(searchText)) {
|
||||
map.put(ParamConstant.KEY_SEARCH_TEXT, searchText);
|
||||
}
|
||||
if (group != null) {
|
||||
Map<String, Object> filter = new HashMap<>();
|
||||
filter.put(ParamConstant.KEY_OPERATOR, group.operator.name().toLowerCase());
|
||||
filter.put(ParamConstant.KEY_CONDITIONS, group.conditions);
|
||||
map.put(ParamConstant.KEY_FILTER, filter);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加AND逻辑条件组到构建器
|
||||
*
|
||||
* @param consumer Builder消费者,用于构建子条件
|
||||
* @return 当前Builder实例,支持链式调用
|
||||
* @return 新建的分组实例,支持链式调用添加子条件
|
||||
*/
|
||||
public Builder and(Consumer<Builder> consumer) {
|
||||
Builder builder = new Builder();
|
||||
consumer.accept(builder);
|
||||
params.put(ParamConstant.KEY_AND, builder);
|
||||
return this;
|
||||
public FilterGroup and() {
|
||||
if (group == null) {
|
||||
group = new FilterGroup(ParamConstant.Operator.AND);
|
||||
} else {
|
||||
if (group.operator != ParamConstant.Operator.AND) {
|
||||
throw new IllegalArgumentException(" 当前是 " + group.operator + " 条件组不允许修改为 AND 条件");
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加OR逻辑条件组到构建器
|
||||
*
|
||||
* @param consumer Builder消费者,用于构建子条件
|
||||
* @return 当前Builder实例,支持链式调用
|
||||
* @return 新建的分组实例,支持链式调用添加子条件
|
||||
*/
|
||||
public Builder or(Consumer<Builder> consumer) {
|
||||
Builder builder = new Builder();
|
||||
consumer.accept(builder);
|
||||
params.put(ParamConstant.KEY_OR, builder);
|
||||
return this;
|
||||
public FilterGroup or() {
|
||||
if (group == null) {
|
||||
group = new FilterGroup(ParamConstant.Operator.OR);
|
||||
} else {
|
||||
if (group.operator != ParamConstant.Operator.OR) {
|
||||
throw new IllegalArgumentException(" 当前是 " + group.operator + " 条件组不允许修改为 OR 条件");
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤条件组,支持 AND/OR 逻辑组合
|
||||
*/
|
||||
public static class FilterGroup {
|
||||
private ParamConstant.Operator operator = ParamConstant.Operator.AND;
|
||||
List<Map<String, Object>> conditions = new java.util.ArrayList<>();
|
||||
|
||||
public FilterGroup(ParamConstant.Operator operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
// 添加叶子条件:equal(支持 ignoreNull)
|
||||
public FilterGroup equals(String field, Object value) {
|
||||
return equals(field, value, false);
|
||||
}
|
||||
|
||||
public FilterGroup equals(String field, Object value, boolean ignoreNull) {
|
||||
if (ignoreNull && isNullOrEmpty(value)) {
|
||||
return this;
|
||||
}
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_equal);
|
||||
leaf.put(ParamConstant.KEY_VALUE, value);
|
||||
if (ignoreNull) {
|
||||
leaf.put(ParamConstant.KEY_IGNORE_NULL, true);
|
||||
}
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加叶子条件:between(对象形式 + 边界 + ignoreNull)
|
||||
public FilterGroup between(String field, LocalDate begin, LocalDate end) {
|
||||
return between(field, begin, end, true, true, false);
|
||||
}
|
||||
|
||||
public FilterGroup between(String field, LocalDate begin, LocalDate end,
|
||||
boolean includeBegin, boolean includeEnd, boolean ignoreNull) {
|
||||
boolean noBegin = begin == null;
|
||||
boolean noEnd = end == null;
|
||||
if (ignoreNull && noBegin && noEnd) {
|
||||
return this;
|
||||
}
|
||||
Map<String, Object> value = new HashMap<>();
|
||||
if (!noBegin) {
|
||||
value.put(ParamConstant.KEY_between_begin, begin);
|
||||
}
|
||||
if (!noEnd) {
|
||||
value.put(ParamConstant.KEY_between_end, end);
|
||||
}
|
||||
value.put(ParamConstant.KEY_INCLUDE_BEGIN, includeBegin);
|
||||
value.put(ParamConstant.KEY_INCLUDE_END, includeEnd);
|
||||
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_BETWEEN);
|
||||
leaf.put(ParamConstant.KEY_VALUE, value);
|
||||
if (ignoreNull) {
|
||||
leaf.put(ParamConstant.KEY_IGNORE_NULL, true);
|
||||
}
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加叶子条件:lessThan(支持 ignoreNull)
|
||||
public FilterGroup lessThan(String field, LocalDate value) {
|
||||
return lessThan(field, value, false);
|
||||
}
|
||||
|
||||
public FilterGroup lessThan(String field, LocalDate value, boolean ignoreNull) {
|
||||
if (ignoreNull && value == null) {
|
||||
return this;
|
||||
}
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_lessThan);
|
||||
leaf.put(ParamConstant.KEY_VALUE, value);
|
||||
if (ignoreNull) {
|
||||
leaf.put(ParamConstant.KEY_IGNORE_NULL, true);
|
||||
}
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加叶子条件:greaterThan(支持 ignoreNull)
|
||||
public FilterGroup greaterThan(String field, LocalDate value) {
|
||||
return greaterThan(field, value, false);
|
||||
}
|
||||
|
||||
public FilterGroup greaterThan(String field, LocalDate value, boolean ignoreNull) {
|
||||
if (ignoreNull && value == null) {
|
||||
return this;
|
||||
}
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_greaterThan);
|
||||
leaf.put(ParamConstant.KEY_VALUE, value);
|
||||
if (ignoreNull) {
|
||||
leaf.put(ParamConstant.KEY_IGNORE_NULL, true);
|
||||
}
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加叶子条件:isNotNull
|
||||
public FilterGroup isNotNull(String field) {
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_isNotNull);
|
||||
leaf.put(ParamConstant.KEY_VALUE, true);
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加叶子条件:like(支持 mode/caseSensitive/ignoreNull)
|
||||
public FilterGroup like(String field, String value) {
|
||||
return like(field, value, ParamConstant.Mode.CONTAINS, false, false);
|
||||
}
|
||||
|
||||
public FilterGroup like(String field, String value, ParamConstant.Mode mode, boolean caseSensitive,
|
||||
boolean ignoreNull) {
|
||||
if (ignoreNull && isNullOrEmpty(value)) {
|
||||
return this;
|
||||
}
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_like);
|
||||
leaf.put(ParamConstant.KEY_VALUE, value);
|
||||
// 使用枚举值作为模式输出
|
||||
leaf.put(ParamConstant.KEY_MODE, mode.name());
|
||||
leaf.put(ParamConstant.KEY_CASE_SENSITIVE, caseSensitive);
|
||||
if (ignoreNull) {
|
||||
leaf.put(ParamConstant.KEY_IGNORE_NULL, true);
|
||||
}
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加叶子条件:notLike(支持 mode/caseSensitive/ignoreNull)
|
||||
public FilterGroup notLike(String field, String value) {
|
||||
return notLike(field, value, ParamConstant.Mode.CONTAINS, false, false);
|
||||
}
|
||||
|
||||
public FilterGroup notLike(String field, String value, ParamConstant.Mode mode, boolean caseSensitive,
|
||||
boolean ignoreNull) {
|
||||
if (ignoreNull && isNullOrEmpty(value)) {
|
||||
return this;
|
||||
}
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_notLike);
|
||||
leaf.put(ParamConstant.KEY_VALUE, value);
|
||||
// 使用枚举值作为模式输出
|
||||
leaf.put(ParamConstant.KEY_MODE, mode.name());
|
||||
leaf.put(ParamConstant.KEY_CASE_SENSITIVE, caseSensitive);
|
||||
if (ignoreNull) {
|
||||
leaf.put(ParamConstant.KEY_IGNORE_NULL, true);
|
||||
}
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加叶子条件:in(空集合防护 + ignoreNull)
|
||||
public FilterGroup in(String field, java.util.Collection<?> values) {
|
||||
return in(field, values, false);
|
||||
}
|
||||
|
||||
public FilterGroup in(String field, java.util.Collection<?> values, boolean ignoreNull) {
|
||||
if (ignoreNull && (values == null || values.isEmpty())) {
|
||||
return this;
|
||||
}
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_in);
|
||||
leaf.put(ParamConstant.KEY_VALUE, values);
|
||||
if (ignoreNull) {
|
||||
leaf.put(ParamConstant.KEY_IGNORE_NULL, true);
|
||||
}
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加叶子条件:notIn(空集合防护 + ignoreNull)
|
||||
public FilterGroup notIn(String field, java.util.Collection<?> values) {
|
||||
return notIn(field, values, false);
|
||||
}
|
||||
|
||||
public FilterGroup notIn(String field, java.util.Collection<?> values, boolean ignoreNull) {
|
||||
if (ignoreNull && (values == null || values.isEmpty())) {
|
||||
return this;
|
||||
}
|
||||
Map<String, Object> leaf = new HashMap<>();
|
||||
leaf.put(ParamConstant.KEY_FIELD, field);
|
||||
leaf.put(ParamConstant.KEY_OPERATOR, ParamConstant.KEY_notIn);
|
||||
leaf.put(ParamConstant.KEY_VALUE, values);
|
||||
if (ignoreNull) {
|
||||
leaf.put(ParamConstant.KEY_IGNORE_NULL, true);
|
||||
}
|
||||
conditions.add(leaf);
|
||||
return this;
|
||||
}
|
||||
|
||||
// 嵌套分组:and / or(保持原有行为)
|
||||
public FilterGroup and() {
|
||||
FilterGroup group = new FilterGroup(ParamConstant.Operator.AND);
|
||||
Map<String, Object> child = new HashMap<>();
|
||||
child.put(ParamConstant.KEY_OPERATOR, ParamConstant.Operator.AND.name().toLowerCase());
|
||||
child.put(ParamConstant.KEY_CONDITIONS, group.conditions);
|
||||
conditions.add(child);
|
||||
return group;
|
||||
}
|
||||
|
||||
public FilterGroup or() {
|
||||
FilterGroup group = new FilterGroup(ParamConstant.Operator.OR);
|
||||
Map<String, Object> child = new HashMap<>();
|
||||
child.put(ParamConstant.KEY_OPERATOR, ParamConstant.Operator.OR.name().toLowerCase());
|
||||
child.put(ParamConstant.KEY_CONDITIONS, group.conditions);
|
||||
conditions.add(child);
|
||||
return group;
|
||||
}
|
||||
|
||||
// 辅助:空值判断(String/Collection/Array/null)
|
||||
private boolean isNullOrEmpty(Object value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
if (value instanceof String s) {
|
||||
return !org.springframework.util.StringUtils.hasText(s);
|
||||
}
|
||||
if (value instanceof java.util.Collection<?> c) {
|
||||
return c.isEmpty();
|
||||
}
|
||||
if (value.getClass().isArray()) {
|
||||
return java.lang.reflect.Array.getLength(value) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,43 @@
|
||||
package com.ecep.contract.constant;
|
||||
|
||||
public class ParamConstant {
|
||||
// 操作符常量
|
||||
public static final String KEY_like = "like";
|
||||
public static final String KEY_notLike = "notLike";
|
||||
public static final String KEY_in = "in";
|
||||
public static final String KEY_notIn = "notIn";
|
||||
public static final String KEY_isNotNull = "isNotNull";
|
||||
public static final String KEY_lessThan = "lessThan";
|
||||
public static final String KEY_greaterThan = "greaterThan";
|
||||
public static final String KEY_between = "between";
|
||||
|
||||
public static final String KEY_BETWEEN = "between";
|
||||
public static final String KEY_between_begin = "begin";
|
||||
public static final String KEY_between_end = "end";
|
||||
public static final String KEY_INCLUDE_BEGIN = "includeBegin";
|
||||
public static final String KEY_INCLUDE_END = "includeEnd";
|
||||
public static final String KEY_equal = "equal";
|
||||
|
||||
public static final String KEY_OR = "or";
|
||||
public static final String KEY_AND = "and";
|
||||
public static final String KEY_SEARCH_TEXT = "searchText";
|
||||
public static final String KEY_FILTER = "filter";
|
||||
public static final String KEY_OPERATOR = "op";
|
||||
public static final String KEY_CONDITIONS = "conditions";
|
||||
// 新增:统一字段与值的键名、like 配置、between 边界、忽略空值
|
||||
public static final String KEY_FIELD = "field";
|
||||
public static final String KEY_VALUE = "value";
|
||||
// 新增:like 的模式键名
|
||||
public static final String KEY_MODE = "mode";
|
||||
public static final String KEY_CASE_SENSITIVE = "caseSensitive";
|
||||
|
||||
public static final String KEY_IGNORE_NULL = "ignoreNull";
|
||||
|
||||
|
||||
|
||||
public enum Operator {
|
||||
OR, AND
|
||||
}
|
||||
// 新增:like 模式枚举
|
||||
public enum Mode {
|
||||
CONTAINS,
|
||||
STARTS_WITH,
|
||||
ENDS_WITH
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,14 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.ds.MyRepository;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.model.Voable;
|
||||
import com.ecep.contract.util.SpecificationUtils;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 实体服务基类
|
||||
@@ -22,6 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
* @param <VO> VO类型
|
||||
* @param <ID> 主键类型
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class EntityService<T extends Voable<VO>, VO, ID> {
|
||||
/**
|
||||
* 获取实体数据访问层接口
|
||||
@@ -52,11 +58,14 @@ public abstract class EntityService<T extends Voable<VO>, VO, ID> {
|
||||
protected abstract Specification<T> buildParameterSpecification(JsonNode paramsNode);
|
||||
|
||||
public Page<VO> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
Specification<T> spec = null;
|
||||
if (paramsNode.has(ParamConstant.KEY_SEARCH_TEXT)) {
|
||||
spec = getSpecification(paramsNode.get(ParamConstant.KEY_SEARCH_TEXT).asText());
|
||||
Specification<T> spec = SpecificationUtils.applySearchText(paramsNode, this::getSpecification);
|
||||
JsonNode filterNode = paramsNode.get(ParamConstant.KEY_FILTER);
|
||||
if (filterNode != null) {
|
||||
Specification<T> childSpec = buildFilterCondition(filterNode);
|
||||
if (childSpec != null) {
|
||||
spec = SpecificationUtils.and(spec, childSpec);
|
||||
}
|
||||
}
|
||||
spec = SpecificationUtils.and(spec, buildParameterSpecification(paramsNode));
|
||||
return findAll(spec, pageable).map(T::toVo);
|
||||
}
|
||||
|
||||
@@ -81,4 +90,144 @@ public abstract class EntityService<T extends Voable<VO>, VO, ID> {
|
||||
Specification<T> spec = getSpecification(searchText);
|
||||
return getRepository().findAll(spec, Pageable.ofSize(10)).getContent();
|
||||
}
|
||||
|
||||
private Specification<T> buildFilterCondition(JsonNode filterNode) {
|
||||
String operatorStr = filterNode.get(ParamConstant.KEY_OPERATOR).asText();
|
||||
|
||||
if (isAndOrOperator(operatorStr)) {
|
||||
if (filterNode.has(ParamConstant.KEY_CONDITIONS)) {
|
||||
JsonNode conditionsNode = filterNode.get(ParamConstant.KEY_CONDITIONS);
|
||||
if (conditionsNode.isArray()) {
|
||||
List<Specification<T>> specs = new java.util.ArrayList<>();
|
||||
|
||||
for (JsonNode conditionNode : conditionsNode) {
|
||||
Specification<T> childSpec = buildFilterCondition(conditionNode);
|
||||
if (childSpec != null) {
|
||||
specs.add(childSpec);
|
||||
}
|
||||
}
|
||||
if (!specs.isEmpty()) {
|
||||
ParamConstant.Operator op = ParamConstant.Operator.AND;
|
||||
if (ParamConstant.Operator.OR.name().equalsIgnoreCase(operatorStr)) {
|
||||
op = ParamConstant.Operator.OR;
|
||||
}
|
||||
return (op == ParamConstant.Operator.AND) ? Specification.allOf(specs)
|
||||
: Specification.anyOf(specs);
|
||||
}
|
||||
} else {
|
||||
log.debug("filterNode 中没有 conditions 数组");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String field = filterNode.get(ParamConstant.KEY_FIELD).asText();
|
||||
if (!StringUtils.hasText(field)) {
|
||||
log.debug("filterNode 中没有 field 字段");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ParamConstant.KEY_equal.equals(operatorStr)) {
|
||||
return buildEqualSpecification(field, filterNode);
|
||||
}
|
||||
if (ParamConstant.KEY_BETWEEN.equals(operatorStr)) {
|
||||
return buildBetweenSpecification(field, filterNode);
|
||||
}
|
||||
if (ParamConstant.KEY_like.equals(operatorStr)) {
|
||||
// TODO 实现 LIKE 操作符的逻辑
|
||||
return null;
|
||||
}
|
||||
if (ParamConstant.KEY_in.equals(operatorStr)) {
|
||||
// TODO 实现 IN 操作符的逻辑
|
||||
return null;
|
||||
}
|
||||
if (ParamConstant.KEY_notIn.equals(operatorStr)) {
|
||||
// TODO 实现 NOT IN 操作符的逻辑
|
||||
return null;
|
||||
}
|
||||
if (ParamConstant.KEY_greaterThan.equals(operatorStr)) {
|
||||
// TODO 实现大于操作符的逻辑
|
||||
return null;
|
||||
}
|
||||
if (ParamConstant.KEY_lessThan.equals(operatorStr)) {
|
||||
// TODO 实现小于操作符的逻辑
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 等于操作符的逻辑
|
||||
*
|
||||
* @param field
|
||||
* @param filterNode
|
||||
* @return
|
||||
*/
|
||||
private Specification<T> buildEqualSpecification(String field, JsonNode filterNode) {
|
||||
// 等于操作符的逻辑
|
||||
JsonNode valueNode = filterNode.get(ParamConstant.KEY_VALUE);
|
||||
if (valueNode == null || valueNode.isNull()) {
|
||||
return (root, query, cb) -> cb.isNull(root.get(field));
|
||||
}
|
||||
return (root, query, cb) -> {
|
||||
// 支持 company.id 这种嵌套属性路径
|
||||
String[] fieldPath = field.split("\\.");
|
||||
Path<?> path = root;
|
||||
for (String segment : fieldPath) {
|
||||
path = path.get(segment);
|
||||
}
|
||||
Class<?> clz = path.getJavaType();
|
||||
if (IdentityEntity.class.isAssignableFrom(clz) && valueNode.isNumber()) {
|
||||
return cb.equal(path.get("id"), valueNode.asInt());
|
||||
}
|
||||
ObjectMapper objectMapper = SpringApp.getBean(ObjectMapper.class);
|
||||
Object value = objectMapper.convertValue(valueNode, clz);
|
||||
// Object value = valueNode.isTextual() ? valueNode.asText()
|
||||
// : valueNode.isNumber() ? valueNode.numberValue()
|
||||
// : valueNode.isBoolean() ? valueNode.asBoolean() : valueNode;
|
||||
return cb.equal(path, value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* BETWEEN 操作符的逻辑
|
||||
*
|
||||
* @param field
|
||||
* @param filterNode
|
||||
* @return
|
||||
*/
|
||||
private <X extends Comparable<? super X>> Specification<T> buildBetweenSpecification(String field,
|
||||
JsonNode filterNode) {
|
||||
// BETWEEN 操作符:要求 value 为数组,且长度=2
|
||||
JsonNode valueNode = filterNode.get(ParamConstant.KEY_VALUE);
|
||||
if (valueNode == null || !valueNode.isArray() || valueNode.size() != 2) {
|
||||
log.debug("BETWEEN 操作符需要 value 为包含两个元素的数组");
|
||||
return null;
|
||||
}
|
||||
JsonNode lowNode = valueNode.get(0);
|
||||
JsonNode highNode = valueNode.get(1);
|
||||
if (lowNode == null || highNode == null || lowNode.isNull() || highNode.isNull()) {
|
||||
log.debug("BETWEEN 操作符的 value 数组元素不能为空");
|
||||
return null;
|
||||
}
|
||||
return (root, query, cb) -> {
|
||||
// 支持嵌套属性路径,如 company.id
|
||||
String[] fieldPath = field.split("\\.");
|
||||
Path<?> path = root;
|
||||
for (String segment : fieldPath) {
|
||||
path = path.get(segment);
|
||||
}
|
||||
Class<X> clz = (Class<X>) path.getJavaType();
|
||||
ObjectMapper objectMapper = SpringApp.getBean(ObjectMapper.class);
|
||||
X lowVal = objectMapper.convertValue(lowNode, clz);
|
||||
X highVal = objectMapper.convertValue(highNode, clz);
|
||||
return cb.between(path.as(clz), lowVal, highVal);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isAndOrOperator(String str) {
|
||||
return ParamConstant.Operator.AND.name().equalsIgnoreCase(str)
|
||||
|| ParamConstant.Operator.OR.name().equalsIgnoreCase(str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
package com.ecep.contract.cloud.rk;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.ecep.contract.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -26,17 +20,20 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.BlackReasonType;
|
||||
import com.ecep.contract.EntityService;
|
||||
import com.ecep.contract.IEntityService;
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.QueryService;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.cloud.CloudInfo;
|
||||
import com.ecep.contract.ds.MyRepository;
|
||||
import com.ecep.contract.ds.company.model.Company;
|
||||
import com.ecep.contract.ds.company.model.CompanyBlackReason;
|
||||
import com.ecep.contract.ds.company.repository.CompanyBlackReasonRepository;
|
||||
import com.ecep.contract.ds.company.repository.CompanyOldNameRepository;
|
||||
import com.ecep.contract.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.ds.other.model.CloudRk;
|
||||
import com.ecep.contract.ds.other.service.SysConfService;
|
||||
import com.ecep.contract.service.VoableService;
|
||||
import com.ecep.contract.util.FileUtils;
|
||||
import com.ecep.contract.util.HttpJsonUtils;
|
||||
import com.ecep.contract.util.MyStringUtils;
|
||||
import com.ecep.contract.util.SpecificationUtils;
|
||||
import com.ecep.contract.vo.CloudRkVo;
|
||||
@@ -53,7 +50,8 @@ import lombok.Data;
|
||||
@Lazy
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "cloud-rk")
|
||||
public class CloudRkService implements IEntityService<CloudRk>, QueryService<CloudRkVo>, VoableService<CloudRk, CloudRkVo> {
|
||||
public class CloudRkService extends EntityService<CloudRk, CloudRkVo, Integer>
|
||||
implements IEntityService<CloudRk>, QueryService<CloudRkVo>, VoableService<CloudRk, CloudRkVo> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CloudRkService.class);
|
||||
|
||||
public static final String KEY_PROXY = "cloud.rk.proxy";
|
||||
@@ -93,14 +91,12 @@ public class CloudRkService implements IEntityService<CloudRk>, QueryService<Clo
|
||||
private boolean nowName;
|
||||
}
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private SysConfService confService;
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CloudRkRepository cloudRKRepository;
|
||||
@Autowired
|
||||
private CompanyOldNameRepository companyOldNameRepository;
|
||||
@Autowired
|
||||
private CompanyBlackReasonRepository companyBlackReasonRepository;
|
||||
|
||||
@Override
|
||||
public CloudRk getById(Integer id) {
|
||||
@@ -113,26 +109,16 @@ public class CloudRkService implements IEntityService<CloudRk>, QueryService<Clo
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<CloudRkVo> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
protected Specification<CloudRk> buildParameterSpecification(JsonNode paramsNode) {
|
||||
Specification<CloudRk> spec = null;
|
||||
if (paramsNode.has("searchText")) {
|
||||
spec = getSpecification(paramsNode.get("searchText").asText());
|
||||
}
|
||||
spec = SpecificationUtils.andParam(spec, paramsNode, "company");
|
||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "cloudId", "customerGrade", "customerScore", "vendorGrade", "vendorScore", "active", "version", "rank", "description");
|
||||
// 可以根据需要添加更多参数处理
|
||||
return findAll(spec, pageable).map(CloudRk::toVo);
|
||||
}
|
||||
|
||||
public Page<CloudRk> findAll(Specification<CloudRk> spec, Pageable pageable) {
|
||||
return cloudRKRepository.findAll(spec, pageable);
|
||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "cloudId", "customerGrade", "customerScore",
|
||||
"vendorGrade", "vendorScore", "active", "version", "rank", "description");
|
||||
return spec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<CloudRk> getSpecification(String searchText) {
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
protected Specification<CloudRk> buildSearchSpecification(String searchText) {
|
||||
return (root, query, builder) -> {
|
||||
Path<Object> company = root.get("company");
|
||||
return builder.or(
|
||||
@@ -143,6 +129,10 @@ public class CloudRkService implements IEntityService<CloudRk>, QueryService<Clo
|
||||
};
|
||||
}
|
||||
|
||||
public Page<CloudRk> findAll(Specification<CloudRk> spec, Pageable pageable) {
|
||||
return cloudRKRepository.findAll(spec, pageable);
|
||||
}
|
||||
|
||||
private void toCompanyBlackReasonList(
|
||||
Company company, BlackReasonType type,
|
||||
JsonNode reason, List<CompanyBlackReason> dbReasons,
|
||||
@@ -386,4 +376,16 @@ public class CloudRkService implements IEntityService<CloudRk>, QueryService<Clo
|
||||
|
||||
cloudRk.setVersion(vo.getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MyRepository<CloudRk, Integer> getRepository() {
|
||||
return cloudRKRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudRk createNewEntity() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'createNewEntity'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.constant.ParamConstant;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@@ -42,7 +43,7 @@ public class SpecificationUtils {
|
||||
* @return 搜索条件为空时返回null,否则返回一个Specification对象
|
||||
*/
|
||||
public static <T> Specification<T> andWith(String searchText,
|
||||
Function<String, Specification<T>> buildSearchSpecification) {
|
||||
Function<String, Specification<T>> buildSearchSpecification) {
|
||||
Specification<T> spec = null;
|
||||
String[] split = null;
|
||||
do {
|
||||
@@ -85,7 +86,8 @@ public class SpecificationUtils {
|
||||
return spec;
|
||||
}
|
||||
|
||||
public static <T> Specification<T> andFieldEqualParam(Specification<T> spec, JsonNode paramsNode, String... fields) {
|
||||
public static <T> Specification<T> andFieldEqualParam(Specification<T> spec, JsonNode paramsNode,
|
||||
String... fields) {
|
||||
for (String field : fields) {
|
||||
if (!paramsNode.has(field)) {
|
||||
continue;
|
||||
@@ -112,8 +114,8 @@ public class SpecificationUtils {
|
||||
return spec;
|
||||
}
|
||||
|
||||
|
||||
public static <T, Y extends Comparable<? super Y>> Specification<T> andFieldBetweenParam(Specification<T> spec, JsonNode paramsNode, String field, Class<Y> valueType) {
|
||||
public static <T, Y extends Comparable<? super Y>> Specification<T> andFieldBetweenParam(Specification<T> spec,
|
||||
JsonNode paramsNode, String field, Class<Y> valueType) {
|
||||
if (paramsNode.has(field)) {
|
||||
JsonNode param = paramsNode.get(field);
|
||||
if (param.isObject()) {
|
||||
@@ -131,4 +133,12 @@ public class SpecificationUtils {
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
public static <T> Specification<T> applySearchText(JsonNode paramsNode,
|
||||
Function<String, Specification<T>> applySearchTextSpecification) {
|
||||
if (paramsNode.has(ParamConstant.KEY_SEARCH_TEXT)) {
|
||||
return applySearchTextSpecification.apply(paramsNode.get(ParamConstant.KEY_SEARCH_TEXT).asText());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user