Commit 17a230bb196da15bb19c834f085b1c4c5dc9079b

Authored by qianbao
1 parent da8e40f1

添加向量库

src/main/java/com/xly/milvus/service/AiGlobalAgentQuestionSqlEmitterService.java
@@ -17,4 +17,5 @@ public interface AiGlobalAgentQuestionSqlEmitterService { @@ -17,4 +17,5 @@ public interface AiGlobalAgentQuestionSqlEmitterService {
17 17
18 Map<String, Object> queryAiGlobalAgentQuestionSqlEmitter(String searchText, String collectionName); 18 Map<String, Object> queryAiGlobalAgentQuestionSqlEmitter(String searchText, String collectionName);
19 19
  20 + void removeAndCreateCollection(String collectionName);
20 } 21 }
21 \ No newline at end of file 22 \ No newline at end of file
src/main/java/com/xly/milvus/service/impl/AiGlobalAgentQuestionSqlEmitterServiceImpl.java
@@ -68,10 +68,13 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -68,10 +68,13 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
68 if(ObjectUtil.isEmpty(sSqlContent)){ 68 if(ObjectUtil.isEmpty(sSqlContent)){
69 sSqlContent = StrUtil.EMPTY; 69 sSqlContent = StrUtil.EMPTY;
70 } 70 }
71 - 71 + //调用数据库插入数据库]
  72 + if(ObjectUtil.isEmpty(data.get("userInput"))){
  73 + Object userInput = ObjectUtil.isNotEmpty(data.get("sUserInput"))?data.get("sUserInput").toString():data.get("userInput");
  74 + data.put("userInput",userInput);
  75 + }
72 // 2. 转换为Milvus格式 76 // 2. 转换为Milvus格式
73 JsonObject row = convertToMilvusRow(data, vector,sQuestion,sSqlContent,cachType,sKey); 77 JsonObject row = convertToMilvusRow(data, vector,sQuestion,sSqlContent,cachType,sKey);
74 -  
75 //创建集合 78 //创建集合
76 // createCollection(collectionName); 79 // createCollection(collectionName);
77 createCollectionIfNotExists(collectionName); 80 createCollectionIfNotExists(collectionName);
@@ -85,11 +88,6 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -85,11 +88,6 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
85 InsertResp insertResp = milvusClient.insert(insertReq); 88 InsertResp insertResp = milvusClient.insert(insertReq);
86 //是否初始化。初始化不需要插入到数据库 89 //是否初始化。初始化不需要插入到数据库
87 if(!isInit){ 90 if(!isInit){
88 - //调用数据库插入数据库]  
89 - if(ObjectUtil.isEmpty(data.get("userInput"))){  
90 - Object userInput = ObjectUtil.isNotEmpty(data.get("sUserInput"))?data.get("sUserInput").toString():data.get("userInput");  
91 - data.put("userInput",userInput);  
92 - }  
93 Map<String, Object> searMap = dynamicExeDbService.getDoProMap(sProName, data); 91 Map<String, Object> searMap = dynamicExeDbService.getDoProMap(sProName, data);
94 dynamicExeDbService.getCallPro(searMap, sProName); 92 dynamicExeDbService.getCallPro(searMap, sProName);
95 } 93 }
@@ -104,9 +102,10 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -104,9 +102,10 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
104 // 2. 设置范围搜索参数 102 // 2. 设置范围搜索参数
105 Map<String, Object> searchParams = new HashMap<>(); 103 Map<String, Object> searchParams = new HashMap<>();
106 searchParams.put("nprobe", 10); 104 searchParams.put("nprobe", 10);
  105 + searchParams.put("radius", 0.94f); // 只返回相似度 >= 0.94 的结果
107 // 对于 IP 度量,相似度范围在 [minScore, maxScore] 106 // 对于 IP 度量,相似度范围在 [minScore, maxScore]
108 - searchParams.put("radius", 0.98); // 最小相似度  
109 - searchParams.put("range_filter", 1); // 最大相似度 107 +// searchParams.put("radius", -1); // 最小相似度
  108 +// searchParams.put("range_filter", 1); // 最大相似度
110 // 1. 确保集合已加载 109 // 1. 确保集合已加载
111 ensureCollectionLoaded(collectionName); 110 ensureCollectionLoaded(collectionName);
112 111
@@ -136,7 +135,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -136,7 +135,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
136 .annsField("vector") // 向量字段名 135 .annsField("vector") // 向量字段名
137 .topK(10) // 返回最相似的10条 136 .topK(10) // 返回最相似的10条
138 .metricType(IndexParam.MetricType.IP) // 内积相似度 137 .metricType(IndexParam.MetricType.IP) // 内积相似度
139 - .outputFields(Arrays.asList("sQuestion", "sSqlContent", "data_id","cachType", "create_time","metadata")) 138 + .outputFields(Arrays.asList("sQuestion", "sSqlContent","sUserInput", "data_id","cachType", "create_time","metadata"))
140 .searchParams(searchParams) 139 .searchParams(searchParams)
141 .build(); 140 .build();
142 // 5. 执行搜索 141 // 5. 执行搜索
@@ -146,6 +145,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -146,6 +145,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
146 if(ObjectUtil.isEmpty(searchResults)){ 145 if(ObjectUtil.isEmpty(searchResults)){
147 return result; 146 return result;
148 } 147 }
  148 + //默认相似度最高的在第一条
149 List<SearchResp.SearchResult> firstResultList = searchResults.get(0); 149 List<SearchResp.SearchResult> firstResultList = searchResults.get(0);
150 if(ObjectUtil.isEmpty(firstResultList)){ 150 if(ObjectUtil.isEmpty(firstResultList)){
151 return result; 151 return result;
@@ -259,6 +259,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -259,6 +259,7 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
259 row.addProperty("sKey", sKey); 259 row.addProperty("sKey", sKey);
260 row.addProperty("data_id", data.get("sId").toString()); 260 row.addProperty("data_id", data.get("sId").toString());
261 row.addProperty("sQuestion", sQuestion); 261 row.addProperty("sQuestion", sQuestion);
  262 + row.addProperty("sUserInput", data.get("userInput").toString());
262 row.addProperty("sSqlContent", sSqlContent); 263 row.addProperty("sSqlContent", sSqlContent);
263 row.addProperty("cachType", cachType); 264 row.addProperty("cachType", cachType);
264 // 创建时间字段 - 必须提供! 265 // 创建时间字段 - 必须提供!
@@ -295,12 +296,6 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -295,12 +296,6 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
295 * 创建集合(定义字段结构) 296 * 创建集合(定义字段结构)
296 */ 297 */
297 private void createCollection(String collectionName) { 298 private void createCollection(String collectionName) {
298 - //删除现有集合  
299 -// DropCollectionReq dropCollectionReq = DropCollectionReq.builder()  
300 -// .collectionName(collectionName)  
301 -// .build();  
302 -// milvusClient.dropCollection(dropCollectionReq);  
303 -  
304 // 定义字段列表 299 // 定义字段列表
305 List<CreateCollectionReq.FieldSchema> fieldSchemas = Arrays.asList( 300 List<CreateCollectionReq.FieldSchema> fieldSchemas = Arrays.asList(
306 // 1. 主键字段 301 // 1. 主键字段
@@ -325,6 +320,13 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -325,6 +320,13 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
325 .name("sQuestion") 320 .name("sQuestion")
326 .dataType(DataType.VarChar) 321 .dataType(DataType.VarChar)
327 .maxLength(5000) 322 .maxLength(5000)
  323 + .description("用户问题(上下文)")
  324 + .build(),
  325 +
  326 + CreateCollectionReq.FieldSchema.builder()
  327 + .name("sUserInput")
  328 + .dataType(DataType.VarChar)
  329 + .maxLength(5000)
328 .description("用户问题") 330 .description("用户问题")
329 .build(), 331 .build(),
330 332
@@ -396,6 +398,19 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent @@ -396,6 +398,19 @@ public class AiGlobalAgentQuestionSqlEmitterServiceImpl implements AiGlobalAgent
396 createIndexesStepByStep(collectionName); 398 createIndexesStepByStep(collectionName);
397 } 399 }
398 400
  401 +
  402 + /**
  403 + * 创建集合(定义字段结构)
  404 + */
  405 + public void removeAndCreateCollection(String collectionName) {
  406 + //删除现有集合
  407 + DropCollectionReq dropCollectionReq = DropCollectionReq.builder()
  408 + .collectionName(collectionName)
  409 + .build();
  410 + milvusClient.dropCollection(dropCollectionReq);
  411 + createCollection(collectionName);
  412 + }
  413 +
399 /* 414 /*
400 * 分步创建索引,便于监控每个索引的状态 415 * 分步创建索引,便于监控每个索引的状态
401 */ 416 */
src/main/java/com/xly/milvus/service/impl/MilvusServiceImpl.java
@@ -170,8 +170,6 @@ public class MilvusServiceImpl implements MilvusService { @@ -170,8 +170,6 @@ public class MilvusServiceImpl implements MilvusService {
170 sb.append(sUserInput).append("已经存在").append("\n"); 170 sb.append(sUserInput).append("已经存在").append("\n");
171 } 171 }
172 //更新数据是否是全局问题,等待朱总确定是否需要 172 //更新数据是否是全局问题,等待朱总确定是否需要
173 -  
174 -  
175 } 173 }
176 }); 174 });
177 if(ObjectUtil.isEmpty(sb)){ 175 if(ObjectUtil.isEmpty(sb)){
@@ -187,15 +185,18 @@ public class MilvusServiceImpl implements MilvusService { @@ -187,15 +185,18 @@ public class MilvusServiceImpl implements MilvusService {
187 public TTSResponseDTO initGlobalAgentQuestion(Map<String, Object> reqMap) { 185 public TTSResponseDTO initGlobalAgentQuestion(Map<String, Object> reqMap) {
188 //全局问题初始化 186 //全局问题初始化
189 List<Map<String,Object>> data = getGlobalAgentQuestion(); 187 List<Map<String,Object>> data = getGlobalAgentQuestion();
  188 + String collectionName = "ai_global_agent_question_sql";
  189 + //删除所有向量
  190 + aiGlobalAgentQuestionSqlEmitterService.removeAndCreateCollection(collectionName);
190 data.forEach(one->{ 191 data.forEach(one->{
191 //查询向量库是否存在 192 //查询向量库是否存在
192 String sSceneId = one.get("sSceneId").toString(); 193 String sSceneId = one.get("sSceneId").toString();
193 String sMethodId = one.get("sMethodId").toString(); 194 String sMethodId = one.get("sMethodId").toString();
194 String sUserInput = one.get("sUserInput").toString(); 195 String sUserInput = one.get("sUserInput").toString();
195 String sSqlContent = one.get("sSqlContent").toString(); 196 String sSqlContent = one.get("sSqlContent").toString();
196 - String searchText = sSceneId+"_"+sMethodId+"_"+sUserInput; 197 + String searchText = String.format("场景:%s 方法:%s 客户问题:%s", sSceneId, sMethodId, sUserInput);
197 if(ObjectUtil.isNotEmpty(sUserInput)){ 198 if(ObjectUtil.isNotEmpty(sUserInput)){
198 - aiGlobalAgentQuestionSqlEmitterService.addAiGlobalAgentQuestionSqlEmitter(searchText,one,sUserInput,sSqlContent,"MYSQL","ai_global_agent_question_sql",true); 199 + aiGlobalAgentQuestionSqlEmitterService.addAiGlobalAgentQuestionSqlEmitter(searchText,one,sUserInput,sSqlContent,"MYSQL",collectionName,true);
199 } 200 }
200 }); 201 });
201 return TTSResponseDTO.builder() 202 return TTSResponseDTO.builder()
src/main/java/com/xly/milvus/service/impl/VectorizationServiceImpl.java
@@ -36,9 +36,12 @@ public class VectorizationServiceImpl implements VectorizationService { @@ -36,9 +36,12 @@ public class VectorizationServiceImpl implements VectorizationService {
36 dev.langchain4j.data.embedding.Embedding embedding = embeddingModel.embed(text).content(); 36 dev.langchain4j.data.embedding.Embedding embedding = embeddingModel.embed(text).content();
37 float[] vectorArray = embedding.vector(); 37 float[] vectorArray = embedding.vector();
38 38
  39 + // L2归一化:将向量长度归一化为1
  40 + float[] normalizedArray = normalizeVector(vectorArray);
  41 +
39 // 转换为List<Float> 42 // 转换为List<Float>
40 List<Float> vector = new ArrayList<>(); 43 List<Float> vector = new ArrayList<>();
41 - for (float v : vectorArray) { 44 + for (float v : normalizedArray) {
42 vector.add(v); 45 vector.add(v);
43 } 46 }
44 47
@@ -49,6 +52,28 @@ public class VectorizationServiceImpl implements VectorizationService { @@ -49,6 +52,28 @@ public class VectorizationServiceImpl implements VectorizationService {
49 } 52 }
50 } 53 }
51 54
  55 + /**
  56 + * L2归一化:使向量的模长为1
  57 + */
  58 + private float[] normalizeVector(float[] vector) {
  59 + // 计算L2范数
  60 + double norm = 0.0;
  61 + for (float v : vector) {
  62 + norm += v * v;
  63 + }
  64 + norm = Math.sqrt(norm);
  65 +
  66 + // 归一化
  67 + if (norm > 0) {
  68 + float[] normalized = new float[vector.length];
  69 + for (int i = 0; i < vector.length; i++) {
  70 + normalized[i] = (float)(vector[i] / norm);
  71 + }
  72 + return normalized;
  73 + }
  74 + return vector;
  75 + }
  76 +
52 @Override 77 @Override
53 public List<List<Float>> batchTextToVector(List<String> texts) { 78 public List<List<Float>> batchTextToVector(List<String> texts) {
54 if (texts == null || texts.isEmpty()) { 79 if (texts == null || texts.isEmpty()) {
src/main/java/com/xly/service/XlyErpService.java
@@ -800,7 +800,7 @@ public class XlyErpService { @@ -800,7 +800,7 @@ public class XlyErpService {
800 **/ 800 **/
801 private Map<String,Object> getDynamicTableCach(UserSceneSession session,String input){ 801 private Map<String,Object> getDynamicTableCach(UserSceneSession session,String input){
802 try{ 802 try{
803 - String searchText = session.getCurrentScene().getSId()+"_"+session.getCurrentTool().getSId()+"_"+input; 803 + String searchText = String.format("场景:%s 方法:%s 客户问题:%s", session.getCurrentScene().getSId(), session.getCurrentTool().getSId(), input);
804 //根据问题查询向量库 804 //根据问题查询向量库
805 Map<String,Object> serMap = aiGlobalAgentQuestionSqlEmitterService.queryAiGlobalAgentQuestionSqlEmitter(searchText, "ai_global_agent_question_sql"); 805 Map<String,Object> serMap = aiGlobalAgentQuestionSqlEmitterService.queryAiGlobalAgentQuestionSqlEmitter(searchText, "ai_global_agent_question_sql");
806 return serMap; 806 return serMap;
src/main/java/com/xly/thread/AiUserAgentQuestionThread.java
@@ -10,7 +10,7 @@ import com.xly.milvus.service.AiGlobalAgentQuestionSqlEmitterService; @@ -10,7 +10,7 @@ import com.xly.milvus.service.AiGlobalAgentQuestionSqlEmitterService;
10 import com.xly.service.DynamicExeDbService; 10 import com.xly.service.DynamicExeDbService;
11 import com.xly.service.RedisService; 11 import com.xly.service.RedisService;
12 import com.xly.util.SqlValidateUtil; 12 import com.xly.util.SqlValidateUtil;
13 -import com.xly.util.SqlWhereHelper; 13 +import com.xly.util.SqlWhereUtil;
14 import dev.langchain4j.data.message.ChatMessage; 14 import dev.langchain4j.data.message.ChatMessage;
15 import dev.langchain4j.data.message.ChatMessageType; 15 import dev.langchain4j.data.message.ChatMessageType;
16 import java.util.HashMap; 16 import java.util.HashMap;
@@ -55,11 +55,12 @@ public class AiUserAgentQuestionThread implements Runnable { @@ -55,11 +55,12 @@ public class AiUserAgentQuestionThread implements Runnable {
55 String sReidKey = SqlValidateUtil.getsKey( sSceneId, sMethodId, sQuestionGroupNo); 55 String sReidKey = SqlValidateUtil.getsKey( sSceneId, sMethodId, sQuestionGroupNo);
56 redisService.set(sReidKey,sSqlContent); 56 redisService.set(sReidKey,sSqlContent);
57 } 57 }
58 - String sKey = sSceneId+"_"+sMethodId +"_"+sQuestion; 58 + String searchText = String.format("场景:%s 方法:%s 客户问题:%s", sSceneId, sMethodId, sQuestion);
  59 + // sSceneId+"_"+sMethodId +"_"+sQuestion;
59 // SqlValidateUtil.getsKey( sSceneId, sMethodId, SqlValidateUtil.getsQuestion(session.getSUserQuestionList())); 60 // SqlValidateUtil.getsKey( sSceneId, sMethodId, SqlValidateUtil.getsQuestion(session.getSUserQuestionList()));
60 //存入向量库 存在SQL语句并且没有where 并且执行成功 61 //存入向量库 存在SQL语句并且没有where 并且执行成功
61 - if(!SqlWhereHelper.hasWhereButNoCompareOperators(sSqlContent) && ObjectUtil.isNotEmpty(sSqlContent) && bSucess){  
62 - aiGlobalAgentQuestionSqlEmitterService.addAiGlobalAgentQuestionSqlEmitter(sKey,data,sQuestion,sSqlContent,cachType,"ai_global_agent_question_sql",false); 62 + if(!SqlWhereUtil.hasValidConditionAfterClean(sSqlContent) && ObjectUtil.isNotEmpty(sSqlContent) && bSucess){
  63 + aiGlobalAgentQuestionSqlEmitterService.addAiGlobalAgentQuestionSqlEmitter(searchText,data,sQuestion,sSqlContent,cachType,"ai_global_agent_question_sql",false);
63 } 64 }
64 //调用数据库插入数据库 65 //调用数据库插入数据库
65 Map<String, Object> searMap = dynamicExeDbService.getDoProMap(sProName, data); 66 Map<String, Object> searMap = dynamicExeDbService.getDoProMap(sProName, data);
src/main/java/com/xly/util/SqlWhereUtil.java 0 → 100644
  1 +package com.xly.util;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import net.sf.jsqlparser.JSQLParserException;
  6 +import net.sf.jsqlparser.expression.*;
  7 +import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
  8 +import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
  9 +import net.sf.jsqlparser.expression.operators.relational.*;
  10 +import net.sf.jsqlparser.parser.CCJSqlParserUtil;
  11 +import net.sf.jsqlparser.schema.Column;
  12 +import net.sf.jsqlparser.statement.Statement;
  13 +import net.sf.jsqlparser.statement.delete.Delete;
  14 +import net.sf.jsqlparser.statement.select.PlainSelect;
  15 +import net.sf.jsqlparser.statement.select.Select;
  16 +import net.sf.jsqlparser.statement.update.Update;
  17 +import net.sf.jsqlparser.expression.LongValue;
  18 +
  19 +@Slf4j
  20 +public class SqlWhereUtil {
  21 +
  22 + /**
  23 + * 去除为空的条件 和 bCheck=1 的条件
  24 + * 然后判断是否还有有效的 WHERE 条件
  25 + *
  26 + * @param sql 原始SQL
  27 + * @return true: 去除后还有有效条件; false: 去除后没有条件了
  28 + */
  29 + public static boolean hasValidConditionAfterClean(String sql) {
  30 + if (StrUtil.isBlank(sql)) {
  31 + return false;
  32 + }
  33 +
  34 + try {
  35 + Statement statement = CCJSqlParserUtil.parse(sql);
  36 + Expression whereExpression = null;
  37 +
  38 + if (statement instanceof Select) {
  39 + Select select = (Select) statement;
  40 + PlainSelect plainSelect = select.getPlainSelect();
  41 + if (plainSelect != null) {
  42 + whereExpression = plainSelect.getWhere();
  43 + }
  44 + } else if (statement instanceof Update) {
  45 + Update update = (Update) statement;
  46 + whereExpression = update.getWhere();
  47 + } else if (statement instanceof Delete) {
  48 + Delete delete = (Delete) statement;
  49 + whereExpression = delete.getWhere();
  50 + }
  51 +
  52 + if (whereExpression == null) {
  53 + return false;
  54 + }
  55 +
  56 + // 清理条件
  57 + Expression cleanedExpression = cleanExpression(whereExpression);
  58 + return cleanedExpression != null;
  59 +
  60 + } catch (JSQLParserException e) {
  61 + log.warn("SQL 解析失败,使用简单判断: {}", sql, e);
  62 + return hasValidConditionAfterCleanSimple(sql);
  63 + }
  64 + }
  65 +
  66 + /**
  67 + * 递归清理表达式
  68 + * 返回 null 表示整个表达式被清空
  69 + */
  70 + private static Expression cleanExpression(Expression expression) {
  71 + if (expression == null) {
  72 + return null;
  73 + }
  74 +
  75 + // 处理 AND 表达式
  76 + if (expression instanceof AndExpression) {
  77 + AndExpression and = (AndExpression) expression;
  78 + Expression left = cleanExpression(and.getLeftExpression());
  79 + Expression right = cleanExpression(and.getRightExpression());
  80 +
  81 + if (left == null && right == null) {
  82 + return null;
  83 + } else if (left == null) {
  84 + return right;
  85 + } else if (right == null) {
  86 + return left;
  87 + } else {
  88 + return new AndExpression(left, right);
  89 + }
  90 + }
  91 +
  92 + // 处理 OR 表达式
  93 + if (expression instanceof OrExpression) {
  94 + OrExpression or = (OrExpression) expression;
  95 + Expression left = cleanExpression(or.getLeftExpression());
  96 + Expression right = cleanExpression(or.getRightExpression());
  97 +
  98 + if (left == null && right == null) {
  99 + return null;
  100 + } else if (left == null) {
  101 + return right;
  102 + } else if (right == null) {
  103 + return left;
  104 + } else {
  105 + return new OrExpression(left, right);
  106 + }
  107 + }
  108 +
  109 + // 处理括号表达式
  110 + if (expression instanceof Parenthesis) {
  111 + Parenthesis parenthesis = (Parenthesis) expression;
  112 + Expression content = cleanExpression(parenthesis.getExpression());
  113 + if (content == null) {
  114 + return null;
  115 + }
  116 + return new Parenthesis(content);
  117 + }
  118 +
  119 + // 判断是否为需要去除的条件
  120 + if (shouldRemoveCondition(expression)) {
  121 + return null;
  122 + }
  123 +
  124 + return expression;
  125 + }
  126 +
  127 + /**
  128 + * 判断条件是否应该被去除
  129 + */
  130 + private static boolean shouldRemoveCondition(Expression expression) {
  131 + // 1. IS NULL / IS NOT NULL
  132 + if (expression instanceof IsNullExpression) {
  133 + return true;
  134 + }
  135 +
  136 + // 2. 等值比较: = '' , != '' , <> ''
  137 + if (expression instanceof EqualsTo) {
  138 + EqualsTo equals = (EqualsTo) expression;
  139 + if (isEmptyString(equals.getRightExpression()) || isEmptyString(equals.getLeftExpression())) {
  140 + return true;
  141 + }
  142 + // bCheck = 1
  143 + if (isCheckOneCondition(equals)) {
  144 + return true;
  145 + }
  146 + }
  147 +
  148 + // !=
  149 + if (expression instanceof NotEqualsTo) {
  150 + NotEqualsTo notEquals = (NotEqualsTo) expression;
  151 + if (isEmptyString(notEquals.getRightExpression()) || isEmptyString(notEquals.getLeftExpression())) {
  152 + return true;
  153 + }
  154 + }
  155 +
  156 + // 3. 函数判断为空: IFNULL(col, '') = '' , TRIM(col) = '' , NVL(col, '') = '' 等
  157 + if (expression instanceof EqualsTo) {
  158 + EqualsTo equals = (EqualsTo) expression;
  159 + if (isFunctionReturnEmptyCheck(equals)) {
  160 + return true;
  161 + }
  162 + }
  163 +
  164 + // 4. 其他比较操作符中可能包含空值判断
  165 + if (expression instanceof ComparisonOperator) {
  166 + ComparisonOperator comp = (ComparisonOperator) expression;
  167 + if (isEmptyString(comp.getLeftExpression()) || isEmptyString(comp.getRightExpression())) {
  168 + return true;
  169 + }
  170 + }
  171 +
  172 + return false;
  173 + }
  174 +
  175 + /**
  176 + * 判断表达式是否为空字符串常量 ''
  177 + */
  178 + private static boolean isEmptyString(Expression expression) {
  179 + if (expression == null) {
  180 + return false;
  181 + }
  182 + if (expression instanceof StringValue) {
  183 + StringValue stringValue = (StringValue) expression;
  184 + return "".equals(stringValue.getValue());
  185 + }
  186 + return false;
  187 + }
  188 +
  189 + /**
  190 + * 判断是否为 bCheck = 1 或 1 = bCheck
  191 + */
  192 + private static boolean isCheckOneCondition(EqualsTo equals) {
  193 + Expression left = equals.getLeftExpression();
  194 + Expression right = equals.getRightExpression();
  195 +
  196 + // bCheck = 1
  197 + if (isColumnNameCheck(left) && isOneValue(right)) {
  198 + return true;
  199 + }
  200 + // 1 = bCheck
  201 + if (isOneValue(left) && isColumnNameCheck(right)) {
  202 + return true;
  203 + }
  204 + return false;
  205 + }
  206 +
  207 + /**
  208 + * 判断是否为 bCheck 列(不区分大小写)
  209 + */
  210 + private static boolean isColumnNameCheck(Expression expression) {
  211 + if (expression instanceof Column) {
  212 + Column column = (Column) expression;
  213 + String columnName = column.getColumnName();
  214 + return columnName != null && columnName.equalsIgnoreCase("bCheck");
  215 + }
  216 + return false;
  217 + }
  218 +
  219 + /**
  220 + * 判断是否为数字 1
  221 + */
  222 + private static boolean isOneValue(Expression expression) {
  223 + if (expression instanceof LongValue) {
  224 + LongValue longValue = (LongValue) expression;
  225 + return longValue.getValue() == 1;
  226 + }
  227 + return false;
  228 + }
  229 +
  230 + /**
  231 + * 判断是否为函数返回空值判断,如:IFNULL(col, '') = ''
  232 + */
  233 + private static boolean isFunctionReturnEmptyCheck(EqualsTo equals) {
  234 + Expression left = equals.getLeftExpression();
  235 + Expression right = equals.getRightExpression();
  236 +
  237 + // 情况1: 函数 = ''
  238 + if (isNullReturnFunction(left) && isEmptyString(right)) {
  239 + return true;
  240 + }
  241 + // 情况2: '' = 函数
  242 + if (isEmptyString(left) && isNullReturnFunction(right)) {
  243 + return true;
  244 + }
  245 + return false;
  246 + }
  247 +
  248 + /**
  249 + * 判断是否为可能返回 NULL 的函数(在空值判断场景下)
  250 + * 如: IFNULL(col, '') , NVL(col, '') , TRIM(col) , COALESCE(col, '')
  251 + */
  252 + private static boolean isNullReturnFunction(Expression expression) {
  253 + if (!(expression instanceof Function)) {
  254 + return false;
  255 + }
  256 +
  257 + Function function = (Function) expression;
  258 + String funcName = function.getName();
  259 +
  260 + if (funcName == null) {
  261 + return false;
  262 + }
  263 +
  264 + String upperName = funcName.toUpperCase();
  265 +
  266 + // 常见的空值处理函数
  267 + switch (upperName) {
  268 + case "IFNULL":
  269 + case "NVL":
  270 + case "NVL2":
  271 + case "COALESCE":
  272 + case "NULLIF":
  273 + return true;
  274 + case "TRIM":
  275 + case "LTRIM":
  276 + case "RTRIM":
  277 + // TRIM 函数去除空格后判断为空
  278 + return true;
  279 + default:
  280 + return false;
  281 + }
  282 + }
  283 +
  284 + // ===================== 简单判断方法(降级方案) =====================
  285 +
  286 + /**
  287 + * 简单判断:去除条件后是否还有有效条件
  288 + */
  289 + private static boolean hasValidConditionAfterCleanSimple(String sql) {
  290 + if (StrUtil.isBlank(sql)) {
  291 + return false;
  292 + }
  293 +
  294 + String upper = sql.toUpperCase();
  295 +
  296 + // 先找到 WHERE 位置
  297 + int whereIdx = upper.indexOf("WHERE");
  298 + if (whereIdx == -1) {
  299 + return false;
  300 + }
  301 +
  302 + // 提取 WHERE 后面的条件
  303 + String whereClause = sql.substring(whereIdx + 5);
  304 +
  305 + // 去除 GROUP BY、ORDER BY、LIMIT 之后的内容
  306 + int groupIdx = upper.indexOf("GROUP BY", whereIdx);
  307 + int orderIdx = upper.indexOf("ORDER BY", whereIdx);
  308 + int limitIdx = upper.indexOf("LIMIT", whereIdx);
  309 + int endIdx = sql.length();
  310 + if (groupIdx != -1) endIdx = Math.min(endIdx, groupIdx);
  311 + if (orderIdx != -1) endIdx = Math.min(endIdx, orderIdx);
  312 + if (limitIdx != -1) endIdx = Math.min(endIdx, limitIdx);
  313 +
  314 + String condition = sql.substring(whereIdx + 5, endIdx);
  315 +
  316 + // 去除需要过滤的条件
  317 + String cleaned = condition;
  318 +
  319 + // 去除 IS NULL / IS NOT NULL
  320 + cleaned = cleaned.replaceAll("(?i)\\bIS\\s+(NOT\\s+)?NULL\\b", "");
  321 +
  322 + // 去除 = '' / != '' / <> ''
  323 + cleaned = cleaned.replaceAll("(?i)\\s*(=|!=|<>)\\s*''\\s*", "");
  324 + cleaned = cleaned.replaceAll("(?i)\\s*''\\s*(=|!=|<>)\\s*", "");
  325 +
  326 + // 去除 bCheck = 1
  327 + cleaned = cleaned.replaceAll("(?i)\\bbCheck\\s*=\\s*1\\b", "");
  328 +
  329 + // 去除函数空值判断: IFNULL(..., '') = ''
  330 + cleaned = cleaned.replaceAll("(?i)\\b(IFNULL|NVL|COALESCE|TRIM)\\s*\\([^)]+\\)\\s*=\\s*''", "");
  331 + cleaned = cleaned.replaceAll("(?i)\\b''\\s*=\\s*(IFNULL|NVL|COALESCE|TRIM)\\s*\\([^)]+\\)", "");
  332 +
  333 + // 去除多余的 AND/OR
  334 + cleaned = cleaned.replaceAll("(?i)\\s+(AND|OR)\\s+", " ");
  335 + cleaned = cleaned.trim();
  336 +
  337 + // 判断是否还有有效条件(非空且不是纯粹的 true/false)
  338 + if (StrUtil.isBlank(cleaned)) {
  339 + return false;
  340 + }
  341 +
  342 + // 如果只剩下 1=1 或 true 等恒真条件,也算无效
  343 + if (cleaned.matches("(?i)^\\s*(1\\s*=\\s*1|true)\\s*$")) {
  344 + return false;
  345 + }
  346 +
  347 + return true;
  348 + }
  349 +}
0 \ No newline at end of file 350 \ No newline at end of file