本文档包含 DynamicConditionUtil 工具类源码及详细注释,支持的查询参数说明,以及两个基于该工具类的 Spring Boot Controller 示例接口代码和使用说明。
一、DynamicConditionUtil 工具类源码及说明
package com.wpglb.dms.orders.utils;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.lang.reflect.Field;
import java.util.Map;
/**
* 分页查询接口说明
*
* 支持的查询参数格式(字段名 + 操作符):
*
* - field_eq=value // 等于(=)
* - field_ne=value // 不等于(!=)
* - field_gt=value // 大于(>)
* - field_lt=value // 小于(<)
* - field_ge=value // 大于等于(>=)
* - field_le=value // 小于等于(<=)
* - field_like=value // 模糊匹配(LIKE '%value%')
* - field_in=val1,val2 // IN 查询(多个值逗号分隔)
* - field_between=val1,val2 // BETWEEN 范围查询(两个值逗号分隔)
*
* 特殊说明:
* - 支持字段前带表别名前缀,格式如:a.orderNo_eq、b.status_in
* 用于多表关联查询,前缀会原样拼接到SQL字段前,如 a.order_no、b.status
* - 参数字段名保持与实体字段名一致,支持驼峰或下划线格式
* - 多个参数间自动以 AND 连接,构成复合查询条件
* - 对于时间或数字类型的 BETWEEN 范围查询,前端需传递两个有效值,格式示例:completionTime_between=2024-01-01 00:00:00,2024-01-31 23:59:59
* - LIKE 查询自动在两边添加百分号,进行模糊匹配
* - IN 查询参数以英文逗号分隔多个值,自动转换成 SQL 的 IN ('val1','val2',...)
*
* 示例请求:
* GET /truck-loading-list/page
* ?orderNo_eq=ABC123
* &status_in=NEW,FINISHED
* &completionTime_ge=2024-01-01
* &completionTime_le=2024-01-31
* &weight_between=10,50
* &a.createUserId_eq=1001
* &b.carrierName_like=顺丰
*
* 生成的 SQL 示例(假设表别名a对应主表,b为关联表):
*
* SELECT a.*, b.carrier_name
* FROM truck_loading_list a
* LEFT JOIN carrier_info b ON a.carrier_id = b.id
* WHERE a.order_no = 'ABC123'
* AND a.status IN ('NEW','FINISHED')
* AND a.completion_time >= '2024-01-01'
* AND a.completion_time <= '2024-01-31'
* AND a.weight BETWEEN 10 AND 50
* AND a.create_user_id = 1001
* AND b.carrier_name LIKE '%顺丰%'
* ORDER BY a.id DESC
* LIMIT 0, 10;
*
* 实现原理:
* 1. 前端传递参数时,参数名格式固定为【字段名_操作符】,如 orderNo_eq、status_in。
* 2. 后端通过反射或手动解析参数名,拆分出字段名和操作符。
* 3. 如果字段名前带表别名(如 a., b.),直接拼接到数据库字段前面,用于多表查询。
* 4. 针对不同操作符,调用 MyBatis-Plus QueryWrapper 对应方法,如 eq(), ne(), gt(), lt(), ge(), le(), like(), in(), between()。
* 5. 对于 between 操作,拆分传入的两个值,分别作为开始和结束边界传给 QueryWrapper.between()。
* 6. 对于 like 操作,自动在值前后添加 `%` 以实现模糊匹配。
* 7. 对于 in 操作,拆分逗号分隔的值构造集合传入 QueryWrapper.in()。
* 8. 最终构造好的 QueryWrapper 传给 MyBatis-Plus 的分页查询方法,实现动态多条件组合查询。
*
* 注意事项:
* - 参数中的时间字符串需保证格式一致,建议后端统一解析为 LocalDateTime 或 Date 类型。
* - 实体字段和数据库字段名应保持映射一致,或者通过@TableField注解指定。
* - 支持多条件自动组合为 AND 关系,复杂 OR 条件需额外扩展。
*/
public class DynamicConditionUtil {
private static final List<String> OPERATORS = Arrays.asList("eq", "ne", "gt", "lt", "ge", "le", "like", "in", "between");
/**
* 构建 QueryWrapper 查询条件
*/
public static <T> QueryWrapper<T> buildWrapper(Class<T> entityClass) {
QueryWrapper<T> wrapper = new QueryWrapper<>();
Map<String, String[]> paramMap = getCurrentHttpServletRequest().getParameterMap();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
String rawKey = entry.getKey();
String[] values = entry.getValue();
if (values == null || values.length == 0 || StringUtils.isBlank(values[0])) {
continue;
}
String value = values[0];
// 处理前缀(如 a.fieldName_op)
String tableAlias = null;
String fieldAndOp = rawKey;
if (rawKey.contains(".")) {
String[] split = rawKey.split("\\.", 2);
tableAlias = split[0];
fieldAndOp = split[1];
}
String fieldName = fieldAndOp;
String operator = "eq";
for (String op : OPERATORS) {
if (fieldAndOp.endsWith("_" + op)) {
fieldName = fieldAndOp.substring(0, fieldAndOp.length() - op.length() - 1);
operator = op;
break;
}
}
String columnName = (tableAlias != null ? tableAlias + "." : "") + camelToUnderline(fieldName);
// 构造条件
switch (operator) {
case "eq" -> wrapper.eq(columnName, value);
case "ne" -> wrapper.ne(columnName, value);
case "gt" -> wrapper.gt(columnName, value);
case "lt" -> wrapper.lt(columnName, value);
case "ge" -> wrapper.ge(columnName, value);
case "le" -> wrapper.le(columnName, value);
case "like" -> wrapper.like(columnName, value);
case "in" -> wrapper.in(columnName, Arrays.asList(value.split(",")));
case "between" -> {
String[] range = value.split(",");
if (range.length == 2) {
wrapper.between(columnName, range[0], range[1]);
}
}
}
}
return wrapper;
}
/**
* 获取当前请求
*/
private static HttpServletRequest getCurrentHttpServletRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return Objects.requireNonNull(requestAttributes).getRequest();
}
/**
* 驼峰转下划线
*/
private static String camelToUnderline(String str) {
if (str == null) return null;
Matcher matcher = Pattern.compile("[A-Z]").matcher(str);
StringBuilder sb = new StringBuilder(str);
int offset = 0;
while (matcher.find()) {
sb.insert(matcher.start() + offset, "_");
offset++;
}
return sb.toString().toLowerCase();
}
public static <T> QueryWrapper<T> buildWrapper(Map<String, Object> filterMap, Class<T> clazz) {
QueryWrapper<T> wrapper = new QueryWrapper<>();
if (MapUtil.isEmpty(filterMap)) {
return wrapper;
}
for (Map.Entry<String, Object> entry : filterMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value == null || StrUtil.isBlank(value.toString())) continue;
String fieldName = key;
String operator = "eq";
if (key.contains("_")) {
String[] parts = key.split("_", 2);
fieldName = parts[0];
operator = parts[1].toLowerCase();
}
// 解析实体字段对应数据库列名(优先@TableField注解)
String dbColumnName = getTableFieldName(clazz, fieldName);
if (dbColumnName == null) {
dbColumnName = StrUtil.toUnderlineCase(fieldName); // 驼峰转下划线
}
switch (operator) {
case "like":
wrapper.like(dbColumnName, value);
break;
case "in":
wrapper.in(dbColumnName, StrUtil.split(value.toString(), ","));
break;
case "ge":
wrapper.ge(dbColumnName, value);
break;
case "le":
wrapper.le(dbColumnName, value);
break;
case "gt":
wrapper.gt(dbColumnName, value);
break;
case "lt":
wrapper.lt(dbColumnName, value);
break;
case "ne":
wrapper.ne(dbColumnName, value);
break;
case "eq":
default:
wrapper.eq(dbColumnName, value);
break;
}
}
return wrapper;
}
private static <T> String getTableFieldName(Class<T> clazz, String fieldName) {
try {
Field field = clazz.getDeclaredField(fieldName);
TableField tableField = field.getAnnotation(TableField.class);
if (tableField != null && StrUtil.isNotBlank(tableField.value())) {
return tableField.value();
}
} catch (NoSuchFieldException e) {
// 字段不存在,忽略
}
return null;
}
}
二、支持的查询参数说明
查询参数格式
参数名格式:字段名_操作符=值
操作符 说明 SQL 示例
eq 等于 field = value
ne 不等于 field != value
gt 大于 field > value
lt 小于 field < value
ge 大于等于 field >= value
le 小于等于 field <= value
like 模糊匹配 field LIKE '%value%'
in IN查询 field IN ('val1','val2')
between 范围查询 field BETWEEN val1 AND val2
特殊说明
1. 表别名支持:支持字段前带表别名前缀,格式如:a.orderNo_eq、b.status_in
2. 字段名格式:参数字段名保持与实体字段名一致,支持驼峰或下划线格式
3. 条件连接:多个参数间自动以 AND 连接
4. 时间范围查询:时间或数字类型的 BETWEEN 查询需要传递两个有效值
5. LIKE查询:自动在值两边添加百分号
6. IN查询:参数以英文逗号分隔多个值
生成的SQL示例
sql
SELECT a.*, b.carrier_name
FROM truck_loading_list a
LEFT JOIN carrier_info b ON a.carrier_id = b.id
WHERE a.order_no = 'ABC123'
AND a.status IN ('NEW','FINISHED')
AND a.completion_time >= '2024-01-01'
AND a.completion_time <= '2024-01-31'
AND a.weight BETWEEN 10 AND 50
AND a.create_user_id = 1001
AND b.carrier_name LIKE '%顺丰%'
ORDER BY a.id DESC
LIMIT 0, 10;三、示例接口代码
1. TruckLoadingListController 示例
@GetMapping("/NewPage1")
@Operation(summary = "NewPage1")
public R<IPage<TruckLoadingList>> page(@Parameter(hidden = true) Query query, TruckLoadingList truckLoadingList) {
QueryWrapper<TruckLoadingList> wrapper = DynamicConditionUtil.buildWrapper(TruckLoadingList.class);
IPage<TruckLoadingList> pages = truckLoadingService.page(Condition.getPage(query), wrapper);
return R.data(pages);
}
@PostMapping("NewPage2")
@Operation(summary = "NewPage2")
public R<IPage<TruckLoadingList>> page(@RequestBody QueryRequest<Map<String, Object>> request) {
Query query = request.getQuery();
Map<String, Object> filter = request.getFilter();
QueryWrapper<TruckLoadingList> wrapper = DynamicConditionUtil.buildWrapper(filter, TruckLoadingList.class);
IPage<TruckLoadingList> page = truckLoadingService.page(Condition.getPage(query), wrapper);
return R.data(page);
}
四、使用说明
1. 前端调用参数示例 GET
GET NewPage1
?orderNo_eq=ABC123
&status_in=NEW,FINISHED
&completionTime_ge=2024-01-01
&completionTime_le=2024-01-31
&weight_between=10,50
&a.createUserId_eq=1001
&b.carrierName_like=顺丰
2. 前端调用参数示例 POST
{
"query": {
"current": 1,
"size": 10
},
"filter": {
"loadingListId_in": "68",
"truckId_eq":"28"
}
}
3. 参数格式要求
● 参数名格式:字段名_操作符
● 支持的操作符:eq, ne, gt, lt, ge, le, like, in, between
● 字段名需对应实体字段名
● 支持驼峰和下划线形式
4. 注意事项
1. 字段名转换:
○ 默认会将驼峰命名字段转换为下划线命名(如 loadingListId → loading_list_id)
○ 可以通过 @TableField 注解自定义列名
2. 空值处理:
○ 值为 null 或空字符串的条件会被自动忽略
3. 类型转换:
○ 值会按照实体类字段类型自动转换
○ 日期时间类型需要确保格式一致
4. 安全性:
○ 所有输入值都会经过 MyBatis-Plus 的参数化处理,防止 SQL 注入
5. 性能建议:
○ 对于大数据量表,建议为常用查询字段添加索引
○ like 查询尽量避免以 % 开头,会导致索引失效
5. 常见问题
Q: 为什么我的条件没有生效?
A: 请检查:
1. 字段名是否正确(包括大小写)
2. 操作符是否正确
3. 值是否为 null 或空字符串
4. 实体类是否有对应的字段定义
Q: 如何实现 OR 条件?
A: 当前版本默认使用 AND 连接所有条件,如需 OR 条件,需要自定义实现或使用 MyBatis-Plus 的 or() 方法手动构建。
Q: 如何添加排序?
A: 可以通过 Query 对象的 ascs 和 descs 属性添加排序条件,或直接在 QueryWrapper 上调用作者:陆飞 创建时间:2026-01-13 17:03
最后编辑:陆飞 更新时间:2026-03-03 10:08
最后编辑:陆飞 更新时间:2026-03-03 10:08