Compare commits

..

2 Commits

Author SHA1 Message Date
wyt
d0c4a6664f Merge remote-tracking branch 'origin/main' 2025-12-11 10:53:34 +08:00
wyt
fb0e30ad63 修改组合筛选查询功能 2025-12-11 10:53:28 +08:00
4 changed files with 304 additions and 79 deletions

View File

@ -3,6 +3,8 @@ package com.zhyc.module.base.mapper;
import com.zhyc.module.base.domain.SheepFile; import com.zhyc.module.base.domain.SheepFile;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.SelectProvider;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -86,5 +88,17 @@ public interface SheepFileMapper
@Param("sheepFile") SheepFile sheepFile @Param("sheepFile") SheepFile sheepFile
); );
// 修改使用 @SelectProvider 并指定 ResultMap
@SelectProvider(type = SheepFileSqlProvider.class, method = "selectByCondition")
@ResultMap("SheepFileResult") // 重要指定使用 XML 中定义的 ResultMap
List<SheepFile> selectByCondition(
@Param("sheepFile") SheepFile sheepFile,
@Param("conditions") Map<String, Object> conditions
);
// 修改获取字段值的方法
@SelectProvider(type = SheepFileSqlProvider.class, method = "selectFieldValues")
List<String> selectFieldValuesByProvider(@Param("fieldName") String fieldName);
} }

View File

@ -0,0 +1,182 @@
package com.zhyc.module.base.mapper;
import com.zhyc.module.base.domain.SheepFile;
import org.apache.ibatis.jdbc.SQL;
import org.springframework.util.StringUtils;
import java.util.Map;
/**
* MyBatis SQL 提供者类
*/
public class SheepFileSqlProvider {
/**
* 生成动态查询的 SQL
*/
public String selectByCondition(Map<String, Object> params) {
SheepFile sheepFile = (SheepFile) params.get("sheepFile");
Map<String, Object> conditions = (Map<String, Object>) params.get("conditions");
// 使用 SQL 构建器
SQL sql = new SQL();
// 重要使用与 XML 中完全相同的字段列表
selectAllColumns(sql);
sql.FROM("sheep_file");
sql.WHERE("is_delete = 0");
// 添加 SheepFile 条件
addSheepFileConditions(sql, sheepFile);
// 添加动态条件
addDynamicConditions(sql, conditions);
sql.ORDER_BY("id DESC");
return sql.toString();
}
/**
* 选择所有列 XML 中的 selectSheepFileVo 保持一致
*/
private void selectAllColumns(SQL sql) {
sql.SELECT(
"id, bs_manage_tags, ranch_id, dr_ranch, sheepfold_id, sheepfold_name, " +
"electronic_tags, variety_id, variety, family, name, gender, birthday, " +
"day_age, month_age, parity, birth_weight, weaning_date, status_id, " +
"weaning_weight, current_weight, weaning_day_age, weaning_daily_gain, " +
"breed_status_id, breed, bs_father_id, father_manage_tags, bs_mother_id, " +
"mother_manage_tags, receptor_id, receptor_manage_tags, father_father_id, " +
"grandfather_manage_tags, father_mother_id, grandmother_manage_tags, " +
"father_id, maternal_grandfather_manage_tags, mother_id, " +
"maternal_grandmother_manage_tags, mating_date, mating_type_id, " +
"preg_date, lambing_date, lambing_day, mating_day, gestation_day, " +
"expected_date, post_lambing_day, lactation_day, anestrous_day, " +
"mating_counts, mating_total, miscarriage_counts, comment, " +
"controlled, body, breast, source, source_date, source_ranch_id, " +
"source_ranch, update_by, update_time, create_by, create_time, " +
"is_delete"
);
}
/**
* 添加 SheepFile 对象中的条件
*/
private void addSheepFileConditions(SQL sql, SheepFile sheepFile) {
if (sheepFile == null) {
return;
}
if (StringUtils.hasText(sheepFile.getBsManageTags())) {
sql.WHERE("bs_manage_tags LIKE CONCAT('%', #{sheepFile.bsManageTags}, '%')");
}
if (StringUtils.hasText(sheepFile.getElectronicTags())) {
sql.WHERE("electronic_tags LIKE CONCAT('%', #{sheepFile.electronicTags}, '%')");
}
if (StringUtils.hasText(sheepFile.getDrRanch())) {
sql.WHERE("dr_ranch LIKE CONCAT('%', #{sheepFile.drRanch}, '%')");
}
if (StringUtils.hasText(sheepFile.getVariety())) {
sql.WHERE("variety LIKE CONCAT('%', #{sheepFile.variety}, '%')");
}
if (StringUtils.hasText(sheepFile.getName())) {
sql.WHERE("name LIKE CONCAT('%', #{sheepFile.name}, '%')");
}
if (sheepFile.getGender() != null) {
sql.WHERE("gender = #{sheepFile.gender}");
}
if (sheepFile.getStatusId() != null) {
sql.WHERE("status_id = #{sheepFile.statusId}");
}
if (StringUtils.hasText(sheepFile.getBreed())) {
sql.WHERE("breed LIKE CONCAT('%', #{sheepFile.breed}, '%')");
}
}
/**
* 简化的动态条件处理
*/
private void addDynamicConditions(SQL sql, Map<String, Object> conditions) {
if (conditions == null || conditions.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : conditions.entrySet()) {
String field = entry.getKey();
Object value = entry.getValue();
if (value == null) {
continue;
}
String strValue = value.toString();
// 处理空值条件
if ("IS_NULL".equals(strValue)) {
sql.WHERE(field + " IS NULL");
} else if ("NOT_NULL".equals(strValue)) {
sql.WHERE(field + " IS NOT NULL");
}
// 处理范围条件
else if (strValue.startsWith("GT:") || strValue.startsWith("LT:") ||
strValue.startsWith("GE:") || strValue.startsWith("LE:")) {
String operator = strValue.substring(0, 2);
String numValue = strValue.substring(3);
if (isNumeric(numValue)) {
sql.WHERE(field + " " + operator + " " + numValue);
}
}
// 处理IN条件
else if (strValue.contains(",")) {
String[] values = strValue.split(",");
StringBuilder inClause = new StringBuilder(field + " IN (");
for (int i = 0; i < values.length; i++) {
inClause.append("'").append(values[i].trim()).append("'");
if (i < values.length - 1) {
inClause.append(",");
}
}
inClause.append(")");
sql.WHERE(inClause.toString());
}
// 默认条件使用等值查询
else {
sql.WHERE(field + " = #{conditions." + field + "}");
}
}
}
/**
* 判断字符串是否为数字
*/
private boolean isNumeric(String str) {
if (str == null || str.trim().isEmpty()) {
return false;
}
try {
Double.parseDouble(str.trim());
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* 生成获取字段值的 SQL
*/
public String selectFieldValues(Map<String, Object> params) {
String fieldName = (String) params.get("fieldName");
SQL sql = new SQL();
sql.SELECT("DISTINCT " + fieldName + " AS field_value");
sql.FROM("sheep_file");
sql.WHERE(fieldName + " IS NOT NULL");
sql.WHERE(fieldName + " != ''");
sql.WHERE(fieldName + " != 'null'");
sql.ORDER_BY(fieldName + " ASC");
return sql.toString();
}
}

View File

@ -72,58 +72,96 @@ public class SheepFileServiceImpl implements ISheepFileService {
/** /**
* 获取指定字段的唯一值列表 * 获取指定字段的唯一值列表
*
* 这个方法实现了获取字段唯一值的核心逻辑
* 包含安全验证和业务处理
*
* @param fieldName 字段名
* @return 该字段的所有唯一值列表
* @throws IllegalArgumentException 当字段名不在白名单中时抛出异常
*/ */
@Override @Override
public List<String> getFieldValues(String fieldName) { public List<String> getFieldValues(String fieldName) {
// 第一步安全性验证 - 防止SQL注入攻击 // 第一步安全性验证 - 防止SQL注入攻击
// 只允许预定义的字段名确保查询的安全性
if (!isValidFieldName(fieldName)) { if (!isValidFieldName(fieldName)) {
// 如果字段名不在白名单中抛出异常并记录日志
throw new IllegalArgumentException("非法的字段名: " + fieldName + ",请检查字段名是否正确"); throw new IllegalArgumentException("非法的字段名: " + fieldName + ",请检查字段名是否正确");
} }
// 第二步调用Mapper层执行数据库查询 // 第二步调用新的 Provider 方法
// 这里会执行类似 SELECT DISTINCT fieldName FROM sheep_file 的SQL // 注意这里调用的是 selectFieldValuesByProvider不是 selectFieldValues
List<String> fieldValues = sheepFileMapper.selectFieldValues(fieldName); return sheepFileMapper.selectFieldValuesByProvider(fieldName);
// 第三步返回查询结果
return fieldValues;
} }
@Override @Override
public List<SheepFile> selectSheepFileListByCondition(Map<String, Object> params, SheepFile sheepFile) { public List<SheepFile> selectSheepFileListByCondition(Map<String, Object> params, SheepFile sheepFile) {
// 验证参数中的字段名防止SQL注入 // 验证并处理参数
if (params != null && !params.isEmpty()) { Map<String, Object> safeConditions = processConditions(params);
Map<String, Object> safeParams = new HashMap<>();
for (Map.Entry<String, Object> entry : params.entrySet()) { // 调用新的 Provider 方法
String fieldName = entry.getKey(); List<SheepFile> result = sheepFileMapper.selectByCondition(sheepFile, safeConditions);
Object value = entry.getValue();
// 将前端字段名转换为数据库字段名
String dbFieldName = convertToDbFieldName(fieldName);
// 验证字段名是否安全使用白名单 return result;
if (isValidFieldName(dbFieldName)) {
safeParams.put(dbFieldName, value);
} else {
// 记录日志或抛出异常
System.out.println("警告:忽略非法字段名: " + fieldName);
}
}
return sheepFileMapper.selectSheepFileListByCondition(safeParams, sheepFile); }
/**
* 处理条件参数确保安全
*/
private Map<String, Object> processConditions(Map<String, Object> params) {
if (params == null || params.isEmpty()) {
return new HashMap<>();
} }
// 如果没有额外参数使用原有的查询方法 Map<String, Object> safeParams = new HashMap<>();
return sheepFileMapper.selectSheepFileList(sheepFile);
for (Map.Entry<String, Object> entry : params.entrySet()) {
String fieldName = entry.getKey();
Object value = entry.getValue();
// 将前端字段名转换为数据库字段名
String dbFieldName = convertToDbFieldName(fieldName);
// 验证字段名是否安全
if (isValidFieldName(dbFieldName)) {
// 处理值确保不是 Character 类型
Object safeValue = value;
if (value != null) {
String strValue = value.toString();
// 特殊处理范围条件确保数字值的安全性
if (strValue.startsWith("GT:") || strValue.startsWith("LT:") ||
strValue.startsWith("GE:") || strValue.startsWith("LE:")) {
String numPart = strValue.substring(3);
// 验证数字部分是否安全防止 SQL 注入
if (isNumeric(numPart)) {
safeValue = strValue;
} else {
// 如果不是数字忽略这个条件
System.out.println("警告:范围条件的值不是数字: " + fieldName + " = " + strValue);
continue;
}
} else {
// 其他值直接使用字符串
safeValue = strValue;
}
}
safeParams.put(dbFieldName, safeValue);
} else {
// 记录日志
System.out.println("警告:忽略非法字段名: " + fieldName + " -> " + dbFieldName);
}
}
return safeParams;
}
/**
* 判断字符串是否为数字
*/
private boolean isNumeric(String str) {
if (str == null || str.isEmpty()) {
return false;
}
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
} }
/** /**
@ -131,10 +169,10 @@ public class SheepFileServiceImpl implements ISheepFileService {
*/ */
private String convertToDbFieldName(String fieldName) { private String convertToDbFieldName(String fieldName) {
// 将驼峰命名转换为下划线命名 // 将驼峰命名转换为下划线命名
String result = fieldName.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase(); return fieldName.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
return result;
} }
/** /**
* 扩展字段名白名单验证 * 扩展字段名白名单验证
*/ */

View File

@ -200,33 +200,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="params != null and !params.isEmpty()"> <if test="params != null and !params.isEmpty()">
<!-- 空值条件 --> <!-- 空值条件 -->
<foreach collection="params.entrySet()" item="value" index="key"> <foreach collection="params.entrySet()" item="value" index="key">
<if test="value == 'IS_NULL'"> <if test="value != null and value.toString() == 'IS_NULL'">
AND ${key} IS NULL AND ${key} IS NULL
</if> </if>
<if test="value == 'NOT_NULL'"> <if test="value != null and value.toString() == 'NOT_NULL'">
AND ${key} IS NOT NULL AND ${key} IS NOT NULL
</if> </if>
</foreach> </foreach>
<!-- 范围条件 --> <!-- 范围条件 -->
<foreach collection="params.entrySet()" item="value" index="key"> <foreach collection="params.entrySet()" item="value" index="key">
<if test="value != null and value.toString().startsWith('GT:')"> <!-- 显式将 value 转换为字符串 -->
AND ${key} &gt; #{value.toString().substring(3)} <if test="value != null and value.toString() != null">
</if> <choose>
<if test="value != null and value.toString().startsWith('LT:')"> <when test="value.toString().startsWith('GT:')">
AND ${key} &lt; #{value.toString().substring(3)} AND ${key} &gt; #{value.toString().substring(3)}
</if> </when>
<if test="value != null and value.toString().startsWith('GE:')"> <when test="value.toString().startsWith('LT:')">
AND ${key} &gt;= #{value.toString().substring(3)} AND ${key} &lt; #{value.toString().substring(3)}
</if> </when>
<if test="value != null and value.toString().startsWith('LE:')"> <when test="value.toString().startsWith('GE:')">
AND ${key} &lt;= #{value.toString().substring(3)} AND ${key} &gt;= #{value.toString().substring(3)}
</when>
<when test="value.toString().startsWith('LE:')">
AND ${key} &lt;= #{value.toString().substring(3)}
</when>
</choose>
</if> </if>
</foreach> </foreach>
<!-- 列表条件 --> <!-- 列表条件 -->
<foreach collection="params.entrySet()" item="value" index="key"> <foreach collection="params.entrySet()" item="value" index="key">
<if test="value != null and value.toString().contains(',')"> <if test="value != null and value.toString() != null and value.toString().contains(',')">
AND ${key} IN AND ${key} IN
<foreach collection="value.toString().split(',')" item="item" open="(" separator="," close=")"> <foreach collection="value.toString().split(',')" item="item" open="(" separator="," close=")">
#{item} #{item}
@ -236,35 +241,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!-- 模糊查询条件(针对文本字段) --> <!-- 模糊查询条件(针对文本字段) -->
<foreach collection="params.entrySet()" item="value" index="key"> <foreach collection="params.entrySet()" item="value" index="key">
<if test="value != null and <if test="value != null and value.toString() != null">
(key == 'bs_manage_tags' or key == 'electronic_tags' or <choose>
key == 'dr_ranch' or key == 'sheepfold_name' or <when test="key == 'bs_manage_tags' or key == 'electronic_tags' or
key == 'variety' or key == 'family' or key == 'dr_ranch' or key == 'sheepfold_name' or
key == 'name' or key == 'breed' or key == 'variety' or key == 'family' or
key == 'father_manage_tags' or key == 'mother_manage_tags' or key == 'name' or key == 'breed' or
key == 'receptor_manage_tags') and key == 'father_manage_tags' or key == 'mother_manage_tags' or
value != 'IS_NULL' and value != 'NOT_NULL' and key == 'receptor_manage_tags'">
!value.toString().startsWith('GT:') and !value.toString().startsWith('LT:') and AND ${key} LIKE CONCAT('%', #{value}, '%')
!value.toString().startsWith('GE:') and !value.toString().startsWith('LE:') and </when>
!value.toString().contains(',')"> <otherwise>
AND ${key} LIKE CONCAT('%', #{value}, '%') <!-- 普通等于条件 -->
</if> AND ${key} = #{value}
</foreach> </otherwise>
</choose>
<!-- 普通等于条件 -->
<foreach collection="params.entrySet()" item="value" index="key">
<if test="value != null and
value != 'IS_NULL' and value != 'NOT_NULL' and
!value.toString().startsWith('GT:') and !value.toString().startsWith('LT:') and
!value.toString().startsWith('GE:') and !value.toString().startsWith('LE:') and
!value.toString().contains(',') and
!(key == 'bs_manage_tags' or key == 'electronic_tags' or
key == 'dr_ranch' or key == 'sheepfold_name' or
key == 'variety' or key == 'family' or
key == 'name' or key == 'breed' or
key == 'father_manage_tags' or key == 'mother_manage_tags' or
key == 'receptor_manage_tags')">
AND ${key} = #{value}
</if> </if>
</foreach> </foreach>
</if> </if>