Browse Source

动态检索新增检索条件
1、单字检索时全部出现
2、单字检索顺序、重复出现权重计算
3、封装方法优化

Lyyyi 13 hours ago
parent
commit
e18a7c4ffc

+ 5 - 4
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -1178,7 +1178,7 @@ namespace OASystem.API.Controllers
         /// <returns></returns>
         /// <returns></returns>
         [HttpGet]
         [HttpGet]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
-        public async Task<IActionResult> GroupItemKeywordSearch1(string keyword)
+        public async Task<IActionResult> GroupItemNativeKeywordSearch (string keyword)
         {
         {
             var jw = JsonView(true, "暂无数据");
             var jw = JsonView(true, "暂无数据");
             if (string.IsNullOrEmpty(keyword)) return Ok(jw);
             if (string.IsNullOrEmpty(keyword)) return Ok(jw);
@@ -1237,13 +1237,14 @@ namespace OASystem.API.Controllers
                 var searchRequest = new DynamicSearchRequest
                 var searchRequest = new DynamicSearchRequest
                 {
                 {
                     Keyword = keyword,
                     Keyword = keyword,
+                    RequireAllSingleChars = true,
                     PageIndex = 1,
                     PageIndex = 1,
-                    PageSize = 9999,
+                    PageSize = 999999,
                     FieldWeights = new Dictionary<string, int>
                     FieldWeights = new Dictionary<string, int>
                     {
                     {
                         { "TeamName", 10 },
                         { "TeamName", 10 },
-                        { "ClientUnit", 8 },
-                        { "ClientName", 6 }
+                        //{ "ClientUnit", 8 },
+                        //{ "ClientName", 6 }
                     },
                     },
                     Filters = new List<SearchFilter>()
                     Filters = new List<SearchFilter>()
                     {
                     {

+ 5 - 0
OASystem/OASystem.Api/OAMethodLib/GenericSearch/DynamicSearchRequest.cs

@@ -45,6 +45,11 @@
         /// 页大小
         /// 页大小
         /// </summary>
         /// </summary>
         public int PageSize { get; set; } = 20;
         public int PageSize { get; set; } = 20;
+
+        /// <summary>
+        /// 是否要求所有单字必须完全出现(新增参数)
+        /// </summary>
+        public bool RequireAllSingleChars { get; set; } = false;
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 136 - 103
OASystem/OASystem.Api/OAMethodLib/GenericSearch/DynamicSearchService.cs

@@ -326,6 +326,7 @@ namespace OASystem.API.OAMethodLib.GenericSearch
             // 构建搜索条件
             // 构建搜索条件
             if (!string.IsNullOrWhiteSpace(request.Keyword))
             if (!string.IsNullOrWhiteSpace(request.Keyword))
             {
             {
+                #region and 构建
                 var searchAnalysis = AnalyzeSearchPattern(request.Keyword);
                 var searchAnalysis = AnalyzeSearchPattern(request.Keyword);
                 var keywordConditions = new List<string>(); // 专门存放关键字相关条件
                 var keywordConditions = new List<string>(); // 专门存放关键字相关条件
 
 
@@ -365,7 +366,51 @@ namespace OASystem.API.OAMethodLib.GenericSearch
                 {
                 {
                     whereConditions.Add($"({string.Join(" OR ", keywordConditions)})");
                     whereConditions.Add($"({string.Join(" OR ", keywordConditions)})");
                 }
                 }
-
+                #endregion
+
+                #region or 构建
+                //var searchAnalysis = AnalyzeSearchPattern(request.Keyword);
+                //var keywordConditions = new List<string>();
+
+                //// 统一处理所有搜索词
+                //var allSearchTerms = new List<string>();
+
+                //// 添加符号分割的片段(去除特殊字符)
+                //allSearchTerms.AddRange(searchAnalysis.SymbolSegments
+                //    .Select(segment => Regex.Replace(segment, @"[^\u4e00-\u9fa5a-zA-Z0-9]", ""))
+                //    .Where(segment => !string.IsNullOrEmpty(segment)));
+
+                //// 添加单字(排除重复)
+                //foreach (var singleChar in searchAnalysis.SingleChars)
+                //{
+                //    var charStr = singleChar.ToString();
+                //    // 只有当单字不在任何符号片段中时才添加
+                //    if (!allSearchTerms.Any(term => term.Contains(charStr)))
+                //    {
+                //        allSearchTerms.Add(charStr);
+                //    }
+                //}
+
+                //// 处理每个搜索词
+                //foreach (var term in allSearchTerms.Distinct())
+                //{
+                //    var fieldConditions = validFields.Select(field =>
+                //    {
+                //        var paramName = $"@term{parameters.Count}";
+                //        parameters.Add(new SugarParameter(paramName, $"%{term}%"));
+                //        return $"{field} LIKE {paramName}";
+                //    });
+                //    keywordConditions.Add($"({string.Join(" OR ", fieldConditions)})");
+                //}
+
+                //// 所有搜索条件使用 AND 连接
+                //if (keywordConditions.Any())
+                //{
+                //    whereConditions.Add($"({string.Join(" AND ", keywordConditions)})");
+                //}
+
+
+                #endregion
             }
             }
 
 
             // 构建过滤条件
             // 构建过滤条件
@@ -405,83 +450,6 @@ namespace OASystem.API.OAMethodLib.GenericSearch
             return (data, totalCount);
             return (data, totalCount);
         }
         }
 
 
-        /// <summary>
-        /// 构建返回字段列表
-        /// </summary>
-        private string BuildReturnFields(List<string> returnFields)
-        {
-            if (returnFields == null || !returnFields.Any())
-            {
-                return "*";
-            }
-
-            // 验证字段是否存在
-            var validFields = ValidateReturnFields(returnFields);
-
-            if (!validFields.Any())
-            {
-                return "*";
-            }
-
-            // 构建字段列表,处理可能的SQL注入和字段名转义
-            var escapedFields = validFields.Select(field =>
-            {
-                // 如果字段包含空格、特殊字符或关键字,用方括号括起来
-                if (field.Contains(" ") || IsSqlKeyword(field) || field.Contains("."))
-                {
-                    return $"[{field}]";
-                }
-                return field;
-            });
-
-            return string.Join(", ", escapedFields);
-        }
-
-
-        /// <summary>
-        /// 验证返回字段的有效性
-        /// </summary>
-        private List<string> ValidateReturnFields(List<string> returnFields)
-        {
-            if (returnFields == null) return new List<string>();
-
-            var entityType = typeof(T);
-            var entityProperties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
-                .Select(p => p.Name)
-                .ToList();
-
-            // 过滤出实体类中存在的字段
-            var validFields = returnFields
-                .Where(field => entityProperties.Contains(field, StringComparer.OrdinalIgnoreCase))
-                .ToList();
-
-            // 记录无效字段警告
-            var invalidFields = returnFields.Except(validFields, StringComparer.OrdinalIgnoreCase).ToList();
-            if (invalidFields.Any())
-            {
-                _logger.LogWarning("发现无效的返回字段: {InvalidFields}", string.Join(", ", invalidFields));
-            }
-
-            return validFields;
-        }
-
-        /// <summary>
-        /// 检查是否为SQL关键字
-        /// </summary>
-        private bool IsSqlKeyword(string word)
-        {
-            var sqlKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
-            {
-                "SELECT", "FROM", "WHERE", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP",
-                "ALTER", "TABLE", "VIEW", "INDEX", "PRIMARY", "KEY", "FOREIGN", "REFERENCES",
-                "AND", "OR", "NOT", "LIKE", "IN", "BETWEEN", "IS", "NULL", "ORDER", "BY",
-                "GROUP", "HAVING", "JOIN", "INNER", "LEFT", "RIGHT", "OUTER", "ON", "AS",
-                "DISTINCT", "TOP", "COUNT", "SUM", "AVG", "MIN", "MAX", "UNION", "ALL"
-            };
-
-            return sqlKeywords.Contains(word);
-        }
-
         /// <summary>
         /// <summary>
         /// 构建基础查询
         /// 构建基础查询
         /// </summary>
         /// </summary>
@@ -565,8 +533,7 @@ namespace OASystem.API.OAMethodLib.GenericSearch
 
 
         #endregion
         #endregion
 
 
-        #region 私有方法 - 匹配度计算
-
+        #region 私有方法 - 匹配度计算含出现位置权重(单字检索时全部出现)
         /// <summary>
         /// <summary>
         /// 在应用层计算匹配度
         /// 在应用层计算匹配度
         /// </summary>
         /// </summary>
@@ -588,7 +555,7 @@ namespace OASystem.API.OAMethodLib.GenericSearch
 
 
             var scoredItems = data.Select(item =>
             var scoredItems = data.Select(item =>
             {
             {
-                var matchResult = CalculateItemMatchScore(item, searchAnalysis, searchFields, fieldWeights);
+                var matchResult = CalculateItemMatchScore(item, searchAnalysis, searchFields, fieldWeights, request.RequireAllSingleChars);
 
 
                 return new SearchResultItem<T>
                 return new SearchResultItem<T>
                 {
                 {
@@ -612,11 +579,24 @@ namespace OASystem.API.OAMethodLib.GenericSearch
             T item,
             T item,
             SearchAnalysis analysis,
             SearchAnalysis analysis,
             List<string> searchFields,
             List<string> searchFields,
-            Dictionary<string, int> fieldWeights)
+            Dictionary<string, int> fieldWeights,
+            bool requireAllSingleChars)
         {
         {
             int totalScore = 0;
             int totalScore = 0;
             var matchFields = new List<MatchFieldInfo>();
             var matchFields = new List<MatchFieldInfo>();
 
 
+            // 新增:根据参数检查是否所有单字都出现
+            if (requireAllSingleChars && analysis.SingleChars.Any())
+            {
+                bool allSingleCharsExist = CheckAllSingleCharsExist(item, analysis.SingleChars, searchFields);
+
+                // 如果要求所有单字必须出现但未完全匹配,直接返回0分
+                if (!allSingleCharsExist)
+                {
+                    return (0, matchFields);
+                }
+            }
+
             foreach (var field in searchFields)
             foreach (var field in searchFields)
             {
             {
                 var fieldValue = GetFieldValue(item, field);
                 var fieldValue = GetFieldValue(item, field);
@@ -659,7 +639,7 @@ namespace OASystem.API.OAMethodLib.GenericSearch
                     }
                     }
                 }
                 }
 
 
-                // 单字匹配
+                // 单字匹配 - 加入位置权重计算
                 foreach (var singleChar in analysis.SingleChars)
                 foreach (var singleChar in analysis.SingleChars)
                 {
                 {
                     int count = fieldValue.Count(c => c == singleChar);
                     int count = fieldValue.Count(c => c == singleChar);
@@ -667,16 +647,28 @@ namespace OASystem.API.OAMethodLib.GenericSearch
                     {
                     {
                         int charScore = count * (int)(weight * 0.3);
                         int charScore = count * (int)(weight * 0.3);
 
 
+                        // 计算位置权重奖励
+                        var positionBonus = CalculateSingleCharPositionBonus(fieldValue, singleChar, weight);
+                        charScore += positionBonus.Bonus;
+
                         if (fieldValue.StartsWith(singleChar.ToString()))
                         if (fieldValue.StartsWith(singleChar.ToString()))
                         {
                         {
                             charScore += weight;
                             charScore += weight;
-                            fieldMatchReasons.Add($"开头单字 '{singleChar}'");
+                            fieldMatchReasons.Add($"开头单字 '{singleChar}' +{weight}");
                         }
                         }
-                        else
+                        else if (positionBonus.Bonus > 0)
                         {
                         {
-                            fieldMatchReasons.Add($"包含单字 '{singleChar}'({count}次)");
+                            fieldMatchReasons.Add($"靠前单字 '{singleChar}' +{positionBonus.Bonus}");
                         }
                         }
 
 
+                        // 添加位置信息到原因
+                        if (positionBonus.FirstPosition >= 0)
+                        {
+                            fieldMatchReasons.Add($"位置{positionBonus.FirstPosition + 1}");
+                        }
+
+                        fieldMatchReasons.Add($"包含单字 '{singleChar}'({count}次)");
+
                         fieldScore += charScore;
                         fieldScore += charScore;
                     }
                     }
                 }
                 }
@@ -701,15 +693,68 @@ namespace OASystem.API.OAMethodLib.GenericSearch
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// 获取显示用的字段值(截断过长的内容)
+        /// 新增:检查所有单字是否在任意字段中出现
         /// </summary>
         /// </summary>
-        private string GetDisplayFieldValue(string fieldValue)
+        private bool CheckAllSingleCharsExist(T item, List<char> singleChars, List<string> searchFields)
         {
         {
-            if (string.IsNullOrEmpty(fieldValue))
+            foreach (var singleChar in singleChars)
+            {
+                bool charExists = false;
+
+                // 检查所有搜索字段中是否包含该单字
+                foreach (var field in searchFields)
+                {
+                    var fieldValue = GetFieldValue(item, field);
+                    if (!string.IsNullOrEmpty(fieldValue) && fieldValue.Contains(singleChar))
+                    {
+                        charExists = true;
+                        break;
+                    }
+                }
+
+                // 如果有一个单字不存在,直接返回false
+                if (!charExists)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// 计算单字位置权重奖励
+        /// </summary>
+        private (int Bonus, int FirstPosition) CalculateSingleCharPositionBonus(string fieldValue, char singleChar, int baseWeight)
+        {
+            var firstPosition = fieldValue.IndexOf(singleChar);
+            if (firstPosition == -1)
+                return (0, -1);
+
+            // 计算位置比例 (0-1),0表示开头,1表示结尾
+            double positionRatio = (double)firstPosition / Math.Max(fieldValue.Length - 1, 1);
+
+            // 位置权重系数:位置越靠前,奖励越高
+            double positionFactor = 1.0 - positionRatio;
+
+            // 计算奖励分数(基于基础权重)
+            int positionBonus = (int)(baseWeight * positionFactor * 0.8); // 最大奖励为基础权重的80%
+
+            // 确保奖励至少为1
+            positionBonus = Math.Max(positionBonus, 1);
+
+            return (positionBonus, firstPosition);
+        }
+
+        /// <summary>
+        /// 获取显示字段值(截断过长的值)
+        /// </summary>
+        private string GetDisplayFieldValue(string fieldValue, int maxLength = 50)
+        {
+            if (string.IsNullOrEmpty(fieldValue) || fieldValue.Length <= maxLength)
                 return fieldValue;
                 return fieldValue;
 
 
-            // 如果字段值过长,截断显示
-            return fieldValue.Length > 50 ? fieldValue.Substring(0, 50) + "..." : fieldValue;
+            return fieldValue.Substring(0, maxLength) + "...";
         }
         }
 
 
         #endregion
         #endregion
@@ -983,18 +1028,6 @@ namespace OASystem.API.OAMethodLib.GenericSearch
             return weights;
             return weights;
         }
         }
 
 
-        /// <summary>
-        /// 验证字段有效性
-        /// </summary>
-        private List<string> ValidateFields(List<string> fields)
-        {
-            var validFields = typeof(T).GetProperties()
-                .Select(p => p.Name)
-                .ToList();
-
-            return fields.Intersect(validFields).ToList();
-        }
-
         /// <summary>
         /// <summary>
         /// 验证搜索字段
         /// 验证搜索字段
         /// </summary>
         /// </summary>