feat: 添加baseMapper方法,将key补齐,修复排序安全性问题,增加多字段排序

This commit is contained in:
lhc
2025-12-24 14:49:22 +08:00
parent 2dc78c8a5c
commit 4988170b0b
6 changed files with 171 additions and 10 deletions

View File

@@ -0,0 +1,27 @@
package com.taixingyiji.base.common;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @className SortItem
* @author lhc
* @date 2025年12月24日 11:16
* @description 描述
* @version 1.0
*/
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
@Data
@ApiModel
public class SortItem implements Serializable {
private static final long serialVersionUID = 5462627800342658554L;
private String field;
private String order;
}

View File

@@ -9,6 +9,7 @@ import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.List;
/**
* (WebPageInfo)实体类
@@ -51,16 +52,38 @@ public class WebPageInfo implements Serializable {
example = "asc")
private String order = ASC;
@ApiModelProperty(
value = "符合排序字段",example = "[{\"field\":\"CREATE_TIME\",\"order\":\"ASC\"}]")
private String sortList;
@ApiModelProperty(
value = "开启缓存"
)
private boolean enableCache = false;
public static boolean isSafeOrderBy(String orderBy) {
if (StringUtils.isBlank(orderBy)) {
return true;
}
// 忽略大小写
return orderBy.matches(
"(?i)^([a-zA-Z0-9_]+\\s+(asc|desc))(\\s*,\\s*[a-zA-Z0-9_]+\\s+(asc|desc))*$"
);
}
public static boolean hasSortList(WebPageInfo webPageInfo) {
return !StringUtils.isBlank(webPageInfo.getSortList());
}
public static boolean hasSort(WebPageInfo webPageInfo) {
return !StringUtils.isBlank(webPageInfo.getSortField());
}
public String getSortSql() {
if(!isSafeOrderBy(this.order)){
throw new ServiceException("排序字段不合法");
}
return this.sortField + " " + this.order;
}
}

View File

@@ -3,21 +3,23 @@ package com.taixingyiji.base.common.utils;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.github.pagehelper.PageInfo;
import com.taixingyiji.base.common.ServiceException;
import com.taixingyiji.base.common.SortItem;
import com.taixingyiji.base.common.WebPageInfo;
import com.github.pagehelper.PageHelper;
import com.taixingyiji.base.common.config.FrameConfig;
import com.taixingyiji.base.module.cache.CacheService;
import com.taixingyiji.base.module.cache.base.BaseCache;
import com.taixingyiji.base.module.cache.emum.CacheType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Supplier;
import static com.taixingyiji.base.common.WebPageInfo.ASC;
import static com.taixingyiji.base.common.WebPageInfo.DESC;
@Component
public class MyPageHelper {
@@ -33,8 +35,50 @@ public class MyPageHelper {
myPageHelper = this;
}
/**
* 校验单个字段和排序方向是否合法
*/
private static boolean isSafeField(String field, String order, Set<String> allowedFields) {
if (StringUtils.isBlank(field)) return false;
// 字段白名单校验,如果不传 allowedFields可传 null则只做格式校验
if (allowedFields != null && !allowedFields.contains(field)) return false;
// 字段名格式安全
if (!field.matches("^[a-zA-Z0-9_]+$")) return false;
// 排序方向安全
if (StringUtils.isBlank(order)) return true; // 默认 asc
return order.equalsIgnoreCase(ASC) || order.equalsIgnoreCase(DESC);
}
/**
* 将 List<SortItem> 转换为 PageHelper 可用 ORDER BY 字符串
*
* @param sortList List<SortItem> 前端传入的排序字段
* @param allowedFields 可选业务白名单字段(可为 null
* @return 安全的 ORDER BY 字符串,如果没有合法字段返回 null
*/
public static String buildOrderBy(List<SortItem> sortList, Set<String> allowedFields) {
if (sortList == null || sortList.isEmpty()) return null;
List<String> orderParts = new ArrayList<>();
for (SortItem item : sortList) {
if (item == null) continue;
String field = item.getField();
String order = item.getOrder();
if (!isSafeField(field, order, allowedFields)) continue;
String dir = DESC.equalsIgnoreCase(order) ? DESC : ASC;
orderParts.add(field + " " + dir);
}
return orderParts.isEmpty() ? null : String.join(", ", orderParts);
}
public static void start(WebPageInfo webPageInfo) {
if (WebPageInfo.hasSort(webPageInfo)) {
if (WebPageInfo.hasSortList(webPageInfo)) {
List<SortItem> sortList = JSONUtil.toList(JSONUtil.parseArray(webPageInfo.getSortList()), SortItem.class);
PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), buildOrderBy(sortList, new HashSet<>())).setAsyncCount(true);
} else if (WebPageInfo.hasSort(webPageInfo)) {
PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), webPageInfo.getSortSql()).setAsyncCount(true);
} else {
PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize()).setAsyncCount(true);
@@ -42,7 +86,10 @@ public class MyPageHelper {
}
public static void noCount(WebPageInfo webPageInfo) {
if (WebPageInfo.hasSort(webPageInfo)) {
if (WebPageInfo.hasSortList(webPageInfo)) {
List<SortItem> sortList = JSONUtil.toList(JSONUtil.parseArray(webPageInfo.getSortList()), SortItem.class);
PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), false).setOrderBy(buildOrderBy(sortList, new HashSet<>()));
} else if (WebPageInfo.hasSort(webPageInfo)) {
PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), false).setOrderBy(webPageInfo.getSortSql());
} else {
PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), false);
@@ -50,7 +97,10 @@ public class MyPageHelper {
}
public static PageInfo<Map<String, Object>> noCount(WebPageInfo webPageInfo, Supplier<List<Map<String, Object>>> querySupplier) {
if (WebPageInfo.hasSort(webPageInfo)) {
if (WebPageInfo.hasSortList(webPageInfo)) {
List<SortItem> sortList = JSONUtil.toList(JSONUtil.parseArray(webPageInfo.getSortList()), SortItem.class);
return PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), buildOrderBy(sortList, new HashSet<>())).count(false).doSelectPageInfo(querySupplier::get);
} else if (WebPageInfo.hasSort(webPageInfo)) {
return PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), webPageInfo.getSortSql()).count(false).doSelectPageInfo(querySupplier::get);
} else {
return PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize()).count(false).doSelectPageInfo(querySupplier::get);
@@ -58,7 +108,10 @@ public class MyPageHelper {
}
public static PageInfo<Map<String, Object>> myStart(WebPageInfo webPageInfo, Supplier<List<Map<String, Object>>> querySupplier) {
if (WebPageInfo.hasSort(webPageInfo)) {
if (WebPageInfo.hasSortList(webPageInfo)) {
List<SortItem> sortList = JSONUtil.toList(JSONUtil.parseArray(webPageInfo.getSortList()), SortItem.class);
return PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), buildOrderBy(sortList, new HashSet<>())).doSelectPageInfo(querySupplier::get);
} else if (WebPageInfo.hasSort(webPageInfo)) {
return PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize(), webPageInfo.getSortSql()).doSelectPageInfo(querySupplier::get);
} else {
return PageHelper.startPage(webPageInfo.getPageNum(), webPageInfo.getPageSize()).doSelectPageInfo(querySupplier::get);
@@ -73,7 +126,7 @@ public class MyPageHelper {
PageInfo<Map<String, Object>> result = myStart(webPageInfo, querySupplier);
jsonObject.set("time", currentTime);
jsonObject.set("count", result.getTotal());
if(result.getTotal() > myPageHelper.frameConfig.getPageMaxCache()){
if (result.getTotal() > myPageHelper.frameConfig.getPageMaxCache()) {
myPageHelper.baseCache.add(CacheType.pageCache.toString(), sql, jsonObject.toString(), String.class);
}
return result;
@@ -102,6 +155,9 @@ public class MyPageHelper {
}
public static void orderBy(String sortField, String order) {
if (!WebPageInfo.isSafeOrderBy(order)) {
throw new ServiceException("order 不合法");
}
PageHelper.orderBy(sortField + " " + order);
}
}

View File

@@ -1,10 +1,14 @@
package com.taixingyiji.base.common.utils;
import static org.thymeleaf.util.StringUtils.split;
public class XssClass {
public static boolean sqlInj(String str){
if(StringUtils.isBlank(str)){
return false;
}
String injStr = "'| and | exec | insert | select | delete | update |"+
" count |*|%| chr | mid | master | truncate | char | declare |;| or |+|,|<script>";
String[] injStra = split(injStr,"|");
@@ -17,6 +21,9 @@ public class XssClass {
}
public static boolean sqlInjLike(String str){
if(StringUtils.isBlank(str)){
return false;
}
String injStr = "'| and | exec | insert | select | delete | update |"+
" count |*| chr | mid | master | truncate | char | declare |;| or |+|,|<script>";
String[] injStra = split(injStr,"|");

View File

@@ -83,7 +83,8 @@ public interface BaseMapper {
List<Map<String, Object>> selectByCondition(String tableName, String fieldList, Condition condition);
PageInfo<Map<String, Object>> selectByCondition(Condition condition, WebPageInfo webPageInfo);
List<Map<String, Object>> selectByConditionAllKey(String tableName, Condition condition);
PageInfo<Map<String, Object>> selectByConditionAllKey(String tableName, Condition condition, WebPageInfo webPageInfo);
<E> PageInfo<Map<String, Object>> selectByCondition(DataMap<E> dataMap, Condition condition, WebPageInfo webPageInfo);
PageInfo<Map<String, Object>> selectByCondition(String tableName, Condition condition, WebPageInfo webPageInfo);

View File

@@ -395,6 +395,33 @@ public class BaseMapperImpl implements BaseMapper {
return tableMapper.useSql(SelectCondition.builder().tableName(tableName).build().getSql());
}
private List<Map<String, Object>> selectListAllKey(Condition condition, String tableName) {
Map<String, Object> params = condition.getParamMap();
params.put("sql", condition.getSql());
List<Map<String, Object>> list =
sqlSessionTemplate.selectList(TABLE_MAPPER_PACKAGE + "useSql", params);
if (list == null || list.isEmpty()) {
return list;
}
// 收集所有可能出现过的 key
Set<String> allKeys = new HashSet<>();
for (Map<String, Object> row : list) {
allKeys.addAll(row.keySet());
}
// 补全缺失字段
for (Map<String, Object> row : list) {
for (String key : allKeys) {
row.putIfAbsent(key, null);
}
}
return list;
}
private List<Map<String, Object>> selectList(Condition condition, String tableName) {
Map<String, Object> params = condition.getParamMap();
String dataTypeConfig = getDataConfig();
@@ -534,6 +561,26 @@ public class BaseMapperImpl implements BaseMapper {
return selectList(condition,tableName);
}
@Override
public List<Map<String, Object>> selectByConditionAllKey(String tableName, Condition condition) {
JudgesNull(tableName, "tableName can not be null!");
condition = condition.toCreatCriteria(DataMap.builder().tableName(tableName).build()).build();
return selectListAllKey(condition,tableName);
}
@Override
public PageInfo<Map<String, Object>> selectByConditionAllKey(String tableName, Condition condition, WebPageInfo webPageInfo) {
JudgesNull(tableName, "tableName can not be null!");
condition = condition.toCreatCriteria(DataMap.builder().tableName(tableName).build()).build();
if (webPageInfo.isEnableCache()) {
Condition finalCondition = condition;
return MyPageHelper.start(webPageInfo, condition.getSql(), () -> selectList(finalCondition,tableName));
}
MyPageHelper.start(webPageInfo);
return new PageInfo<>(selectListAllKey(condition,tableName));
}
@Override
public PageInfo<Map<String, Object>> selectByCondition(Condition condition, WebPageInfo webPageInfo) {
MyPageHelper.start(webPageInfo);