|
|
@@ -1,24 +1,83 @@
|
|
|
package com.etotem.cfc.util;
|
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.util.StringUtils;
|
|
|
|
|
|
+import java.lang.reflect.Field;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
|
|
/**
|
|
|
* 通用排序工具:将前端 sort 参数注入 MyBatis-Plus 查询包装器。
|
|
|
* 支持 LambdaQueryWrapper、QueryWrapper 和 LambdaUpdateWrapper。
|
|
|
+ *
|
|
|
+ * 排序参数格式(前端 SortMixin 产出):
|
|
|
+ * [{column: 'id', order: 'asc'}, {column: 'name', order: 'desc'}]
|
|
|
+ *
|
|
|
+ * 列名映射:驼峰属性名 → 下划线列名(如 displayName → display_name)。
|
|
|
+ * 可通过 snakeMap 提供自定义映射。
|
|
|
+ *
|
|
|
+ * 安全:列名仅限 [a-zA-Z_][a-zA-Z0-9_]*,防止 SQL 注入。
|
|
|
+ *
|
|
|
+ * 双 ORDER BY 防护:有用户排序时,通过反射清除 wrapper 内已有的 orderBy 段并失效缓存,
|
|
|
+ * 避免与 lambda 排序(如 wrapper.orderByDesc(...))产生双 ORDER BY 语法错误。
|
|
|
*/
|
|
|
public class SortUtil {
|
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(SortUtil.class);
|
|
|
+ private static final Pattern SAFE_COLUMN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
|
|
|
+
|
|
|
+ /** 反射缓存:MergeSegments 的 cacheSqlSegment 字段 */
|
|
|
+ private static final Field CACHE_SQL_SEGMENT_FIELD;
|
|
|
+ private static final Field EXPRESSION_FIELD;
|
|
|
+
|
|
|
+ static {
|
|
|
+ Field cacheField = null;
|
|
|
+ Field exprField = null;
|
|
|
+ try {
|
|
|
+ Class<?> mergeClass = Class.forName(
|
|
|
+ "com.baomidou.mybatisplus.core.conditions.segments.MergeSegments");
|
|
|
+ cacheField = mergeClass.getDeclaredField("cacheSqlSegment");
|
|
|
+ cacheField.setAccessible(true);
|
|
|
+
|
|
|
+ Class<?> wrapperClass = Class.forName(
|
|
|
+ "com.baomidou.mybatisplus.core.conditions.AbstractWrapper");
|
|
|
+ exprField = wrapperClass.getDeclaredField("expression");
|
|
|
+ exprField.setAccessible(true);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[SortUtil] 反射初始化失败,将回退到旧行为", e);
|
|
|
+ }
|
|
|
+ CACHE_SQL_SEGMENT_FIELD = cacheField;
|
|
|
+ EXPRESSION_FIELD = exprField;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清除 wrapper 中已有的 ORDER BY 段,并使其缓存失效。
|
|
|
+ * 确保后续追加的 ORDER BY 不会与原有 lambda orderBy 冲突(双 ORDER BY 语法错误)。
|
|
|
+ */
|
|
|
+ static void clearOrderBy(AbstractWrapper<?, ?, ?> wrapper) {
|
|
|
+ try {
|
|
|
+ MergeSegments expression = (MergeSegments) EXPRESSION_FIELD.get(wrapper);
|
|
|
+ if (expression != null) {
|
|
|
+ expression.getOrderBy().clear();
|
|
|
+ if (CACHE_SQL_SEGMENT_FIELD != null) {
|
|
|
+ CACHE_SQL_SEGMENT_FIELD.setBoolean(expression, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[SortUtil] 清除已有 ORDER BY 失败: {}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 对 QueryWrapper 应用排序(从 SortContext 读取排序条件)。
|
|
|
- * 无排序上下文时为空操作(保留原有排序)。
|
|
|
- *
|
|
|
- * @param wrapper QueryWrapper 实例
|
|
|
*/
|
|
|
public static <T> void applySort(QueryWrapper<T> wrapper) {
|
|
|
applySort(wrapper, SortContext.get(), null);
|
|
|
@@ -26,9 +85,6 @@ public class SortUtil {
|
|
|
|
|
|
/**
|
|
|
* 对 LambdaQueryWrapper 应用排序(从 SortContext 读取排序条件)。
|
|
|
- * 无排序上下文时为空操作(保留原有排序)。
|
|
|
- *
|
|
|
- * @param wrapper LambdaQueryWrapper 实例
|
|
|
*/
|
|
|
public static <T> void applySort(LambdaQueryWrapper<T> wrapper) {
|
|
|
applySort(wrapper, SortContext.get(), null);
|
|
|
@@ -36,9 +92,6 @@ public class SortUtil {
|
|
|
|
|
|
/**
|
|
|
* 对 LambdaUpdateWrapper 应用排序(从 SortContext 读取排序条件)。
|
|
|
- * 无排序上下文时为空操作(保留原有排序)。
|
|
|
- *
|
|
|
- * @param wrapper LambdaUpdateWrapper 实例
|
|
|
*/
|
|
|
public static <T> void applySort(LambdaUpdateWrapper<T> wrapper) {
|
|
|
applySort(wrapper, SortContext.get(), null);
|
|
|
@@ -46,10 +99,6 @@ public class SortUtil {
|
|
|
|
|
|
/**
|
|
|
* 对 QueryWrapper 应用排序。如果 sortSpecs 为空则不修改排序(保留原有排序)。
|
|
|
- *
|
|
|
- * @param wrapper QueryWrapper 实例
|
|
|
- * @param sortSpecs 前端传入的排序数组,每项 {column, order}
|
|
|
- * @param snakeMap 驼峰属性名 → 数据库列名 映射(可为 null)
|
|
|
*/
|
|
|
@SuppressWarnings("unchecked")
|
|
|
public static <T> void applySort(QueryWrapper<T> wrapper,
|
|
|
@@ -67,14 +116,13 @@ public class SortUtil {
|
|
|
sb.append(dbCol).append(asc ? " asc" : " desc");
|
|
|
}
|
|
|
if (sb.length() > 11) {
|
|
|
+ clearOrderBy(wrapper);
|
|
|
wrapper.last(sb.toString());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 对 LambdaQueryWrapper 应用排序(通过 last SQL 追加)。
|
|
|
- * 因为 LambdaQueryWrapper 没有通用的 orderBy 重载接受 db 列名字符串,
|
|
|
- * 所以直接通过 last() 追加。
|
|
|
*/
|
|
|
public static <T> void applySort(LambdaQueryWrapper<T> wrapper,
|
|
|
List<Map<String, String>> sortSpecs,
|
|
|
@@ -91,6 +139,7 @@ public class SortUtil {
|
|
|
sb.append(dbCol).append(asc ? " asc" : " desc");
|
|
|
}
|
|
|
if (sb.length() > 11) {
|
|
|
+ clearOrderBy(wrapper);
|
|
|
wrapper.last(sb.toString());
|
|
|
}
|
|
|
}
|
|
|
@@ -113,15 +162,24 @@ public class SortUtil {
|
|
|
sb.append(dbCol).append(asc ? " asc" : " desc");
|
|
|
}
|
|
|
if (sb.length() > 11) {
|
|
|
+ clearOrderBy(wrapper);
|
|
|
wrapper.last(sb.toString());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static String resolveDbColumn(String camelCol, Map<String, String> snakeMap) {
|
|
|
if (snakeMap != null && snakeMap.containsKey(camelCol)) {
|
|
|
- return snakeMap.get(camelCol);
|
|
|
+ String mapped = snakeMap.get(camelCol);
|
|
|
+ if (!SAFE_COLUMN.matcher(mapped).matches()) {
|
|
|
+ throw new IllegalArgumentException("非法的排序列名: " + mapped);
|
|
|
+ }
|
|
|
+ return mapped;
|
|
|
}
|
|
|
// 默认:驼峰转下划线
|
|
|
- return camelCol.replaceAll("([A-Z])", "_$1").toLowerCase();
|
|
|
+ String dbCol = camelCol.replaceAll("([A-Z])", "_$1").toLowerCase();
|
|
|
+ if (!SAFE_COLUMN.matcher(dbCol).matches()) {
|
|
|
+ throw new IllegalArgumentException("非法的排序列名: " + camelCol);
|
|
|
+ }
|
|
|
+ return dbCol;
|
|
|
}
|
|
|
}
|