AdvancedSearchHelper.cs 92 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471
  1. using System.ComponentModel;
  2. using System.Text.RegularExpressions;
  3. namespace OASystem.Infrastructure.Tools
  4. {
  5. /// <summary>
  6. /// 增强版高级搜索助手(支持智能拆分组合词)
  7. /// 支持组合查询:多词条/单词条 + 单字 混合搜索
  8. /// 提供智能搜索条件构建、相关性分析、分阶段搜索等功能
  9. /// </summary>
  10. public class AdvancedSearchHelper
  11. {
  12. #region 公共方法 - 主要搜索功能
  13. /// <summary>
  14. /// 构建增强搜索条件(支持组合查询和智能拆分)
  15. /// </summary>
  16. /// <param name="searchTerm">搜索关键词</param>
  17. /// <param name="searchFields">搜索字段列表</param>
  18. /// <param name="prefix">参数前缀,用于避免参数名冲突</param>
  19. /// <param name="searchMode">搜索模式,默认为自动检测</param>
  20. /// <param name="combinationMode">组合模式,默认智能混合</param>
  21. /// <param name="config">搜索配置选项</param>
  22. /// <returns>构建的SQL条件和参数</returns>
  23. /// <example>
  24. /// <code>
  25. /// var fields = new List&lt;string&gt; { "Name", "Description" };
  26. /// var config = new SearchConfig {
  27. /// EnableSmartSplitting = true, // 启用智能拆分
  28. /// RequireAllWordsForSplit = false
  29. /// };
  30. /// var (condition, parameters) = AdvancedSearchHelper.BuildEnhancedSearchCondition(
  31. /// "重庆科学",
  32. /// fields,
  33. /// "search_",
  34. /// SearchMode.AutoDetect,
  35. /// CombinationMode.SmartMix,
  36. /// config
  37. /// );
  38. /// </code>
  39. /// </example>
  40. public static (string Condition, List<SugarParameter> Parameters) BuildEnhancedSearchCondition(
  41. string searchTerm,
  42. List<string> searchFields,
  43. string prefix = "",
  44. SearchMode searchMode = SearchMode.AutoDetect,
  45. CombinationMode combinationMode = CombinationMode.SmartMix,
  46. SearchConfig config = null)
  47. {
  48. // 参数验证
  49. if (searchFields == null || !searchFields.Any())
  50. throw new ArgumentException("搜索字段列表不能为空", nameof(searchFields));
  51. if (string.IsNullOrWhiteSpace(searchTerm))
  52. return (string.Empty, new List<SugarParameter>());
  53. searchTerm = searchTerm.Trim();
  54. config ??= new SearchConfig();
  55. // 检查搜索词长度
  56. if (searchTerm.Length > config.MaxSearchTermLength)
  57. searchTerm = searchTerm.Substring(0, config.MaxSearchTermLength);
  58. // 分析搜索词结构
  59. var structure = AnalyzeSearchStructure(searchTerm);
  60. // 根据组合模式构建条件
  61. return combinationMode switch
  62. {
  63. CombinationMode.SmartMix => BuildSmartMixCondition(structure, searchFields, prefix, searchMode, config),
  64. CombinationMode.WordsAndChars => BuildWordsAndCharsCondition(structure, searchFields, prefix, config),
  65. CombinationMode.WordsOnly => BuildWordsOnlyCondition(structure, searchFields, prefix, config),
  66. CombinationMode.CharsOnly => BuildCharsOnlyCondition(structure, searchFields, prefix, config),
  67. _ => BuildSmartMixCondition(structure, searchFields, prefix, searchMode, config)
  68. };
  69. }
  70. /// <summary>
  71. /// 构建带相关性信息的搜索条件
  72. /// </summary>
  73. /// <param name="searchTerm">搜索关键词</param>
  74. /// <param name="searchFields">搜索字段列表</param>
  75. /// <param name="prefix">参数前缀</param>
  76. /// <param name="searchMode">搜索模式</param>
  77. /// <param name="config">搜索配置</param>
  78. /// <returns>包含条件、参数和相关性的完整结果</returns>
  79. public static (string Condition, List<SugarParameter> Parameters, RelevanceInfo Relevance)
  80. BuildRelevanceSearchCondition(
  81. string searchTerm,
  82. List<string> searchFields,
  83. string prefix = "",
  84. SearchMode searchMode = SearchMode.AutoDetect,
  85. SearchConfig config = null)
  86. {
  87. config ??= new SearchConfig();
  88. var structure = AnalyzeSearchStructure(searchTerm);
  89. var (condition, parameters) = BuildEnhancedSearchCondition(
  90. searchTerm, searchFields, prefix, searchMode, CombinationMode.SmartMix, config);
  91. var relevance = new RelevanceInfo
  92. {
  93. SearchStructure = structure,
  94. ExpectedMatches = CalculateExpectedMatches(structure, searchFields.Count),
  95. BoostFields = GetBoostFields(structure, searchFields),
  96. ScoringRules = GetScoringRules(structure, config),
  97. MatchStrategy = GetMatchStrategy(structure, config),
  98. GeneratedAt = DateTime.Now
  99. };
  100. return (condition, parameters, relevance);
  101. }
  102. /// <summary>
  103. /// 构建分阶段搜索条件(用于分阶段匹配和降级搜索)
  104. /// </summary>
  105. /// <param name="searchTerm">搜索关键词</param>
  106. /// <param name="searchFields">搜索字段列表</param>
  107. /// <param name="prefix">参数前缀</param>
  108. /// <param name="config">搜索配置</param>
  109. /// <returns>按优先级排序的搜索阶段列表</returns>
  110. public static List<SearchStage> BuildStagedSearchConditions(
  111. string searchTerm,
  112. List<string> searchFields,
  113. string prefix = "",
  114. SearchConfig config = null)
  115. {
  116. config ??= new SearchConfig();
  117. var structure = AnalyzeSearchStructure(searchTerm);
  118. var stages = new List<SearchStage>();
  119. // 阶段1:精确匹配(完全相等)
  120. if (structure.Words.Any() && config.EnableExactMatch)
  121. {
  122. foreach (var word in structure.Words.Where(w => w.Length >= config.MinExactMatchLength))
  123. {
  124. var (condition, parameters) = BuildExactCondition(word, searchFields, $"{prefix}exact_{stages.Count}");
  125. if (!string.IsNullOrEmpty(condition))
  126. {
  127. stages.Add(new SearchStage
  128. {
  129. Stage = 1,
  130. Name = $"精确匹配: {word}",
  131. Condition = condition,
  132. Parameters = parameters,
  133. Priority = 100,
  134. Description = $"完全匹配词语'{word}'",
  135. FallbackToNext = false
  136. });
  137. }
  138. }
  139. }
  140. // 阶段2:开头匹配
  141. if (structure.Words.Any() && config.EnablePrefixMatch)
  142. {
  143. foreach (var word in structure.Words)
  144. {
  145. var (condition, parameters) = BuildPrefixCondition(word, searchFields, $"{prefix}prefix_{stages.Count}");
  146. if (!string.IsNullOrEmpty(condition))
  147. {
  148. stages.Add(new SearchStage
  149. {
  150. Stage = 2,
  151. Name = $"开头匹配: {word}",
  152. Condition = condition,
  153. Parameters = parameters,
  154. Priority = 80,
  155. Description = $"以'{word}'开头",
  156. FallbackToNext = true
  157. });
  158. }
  159. }
  160. }
  161. // 阶段3:词语模糊匹配
  162. if (structure.Words.Any() && config.EnableFuzzySearch)
  163. {
  164. var wordsText = string.Join(" ", structure.Words);
  165. var (condition, parameters) = BuildMultiWordCondition(
  166. wordsText, searchFields, $"{prefix}fuzzy_{stages.Count}",
  167. config.RequireAllWords);
  168. if (!string.IsNullOrEmpty(condition))
  169. {
  170. stages.Add(new SearchStage
  171. {
  172. Stage = 3,
  173. Name = "词语模糊匹配",
  174. Condition = condition,
  175. Parameters = parameters,
  176. Priority = 60,
  177. Description = $"包含词语: {wordsText}",
  178. FallbackToNext = true
  179. });
  180. }
  181. }
  182. // 阶段4:单字匹配
  183. if (structure.SingleChars.Any() && config.EnableCharSearch)
  184. {
  185. var charsText = string.Join("", structure.SingleChars);
  186. var (condition, parameters) = BuildSingleCharCondition(
  187. charsText, searchFields, $"{prefix}chars_{stages.Count}",
  188. config.RequireAllChars);
  189. if (!string.IsNullOrEmpty(condition))
  190. {
  191. stages.Add(new SearchStage
  192. {
  193. Stage = 4,
  194. Name = "单字匹配",
  195. Condition = condition,
  196. Parameters = parameters,
  197. Priority = 40,
  198. Description = $"包含单字: {charsText}",
  199. FallbackToNext = true
  200. });
  201. }
  202. }
  203. // 阶段5:智能拆分匹配(新增阶段)
  204. if (config.EnableSmartSplitting && structure.Words.Any(w => w.Length >= config.MinSplitLength))
  205. {
  206. var longWords = structure.Words.Where(w => w.Length >= config.MinSplitLength).ToList();
  207. foreach (var longWord in longWords)
  208. {
  209. if (IsSplittableCombinationWord(longWord))
  210. {
  211. var (condition, parameters) = BuildSmartSplitConditionForWord(
  212. longWord, searchFields, $"{prefix}split_{stages.Count}", config);
  213. if (!string.IsNullOrEmpty(condition))
  214. {
  215. stages.Add(new SearchStage
  216. {
  217. Stage = 5,
  218. Name = $"智能拆分: {longWord}",
  219. Condition = condition,
  220. Parameters = parameters,
  221. Priority = 50, // 优先级在词语模糊匹配和单字匹配之间
  222. Description = $"智能拆分'{longWord}'为子词或单字",
  223. FallbackToNext = true
  224. });
  225. }
  226. }
  227. }
  228. }
  229. // 阶段6:宽松混合匹配
  230. if (stages.Any(s => s.Stage >= 3 && s.Stage <= 5))
  231. {
  232. var mixedConditions = stages
  233. .Where(s => s.Stage >= 3 && s.Stage <= 5)
  234. .Select(s => s.Condition)
  235. .ToList();
  236. var mixedParameters = stages
  237. .Where(s => s.Stage >= 3 && s.Stage <= 5)
  238. .SelectMany(s => s.Parameters)
  239. .ToList();
  240. if (mixedConditions.Any())
  241. {
  242. stages.Add(new SearchStage
  243. {
  244. Stage = 6,
  245. Name = "宽松混合匹配",
  246. Condition = $"({string.Join(" OR ", mixedConditions)})",
  247. Parameters = mixedParameters,
  248. Priority = 20,
  249. Description = "词语或单字的任意匹配",
  250. FallbackToNext = false
  251. });
  252. }
  253. }
  254. return stages.OrderByDescending(s => s.Priority).ToList();
  255. }
  256. #endregion
  257. #region 核心构建方法
  258. /// <summary>
  259. /// 智能混合构建条件(支持智能拆分组合词)
  260. /// </summary>
  261. private static (string, List<SugarParameter>) BuildSmartMixCondition(
  262. SearchStructure structure,
  263. List<string> searchFields,
  264. string prefix,
  265. SearchMode searchMode,
  266. SearchConfig config)
  267. {
  268. var parameters = new List<SugarParameter>();
  269. var conditions = new List<string>();
  270. // 分析结构复杂度
  271. var complexity = CalculateComplexityScore(structure);
  272. // 检查是否有需要智能拆分的组合词
  273. var longWords = structure.Words.Where(w => w.Length >= config.MinSplitLength).ToList();
  274. var shouldSplitLongWords = longWords.Any() && config.EnableSmartSplitting;
  275. if (shouldSplitLongWords && longWords.Any(w => IsSplittableCombinationWord(w)))
  276. {
  277. // 使用智能拆分策略处理组合词
  278. return BuildSmartSplitCondition(structure, searchFields, prefix, config, longWords);
  279. }
  280. // 智能分析混合类型的特点
  281. var hasLongWords = structure.Words.Any(w => w.Length >= 2);
  282. var hasManyChars = structure.SingleChars.Count >= 3;
  283. var isCharDominant = structure.SingleChars.Count > structure.Words.Count;
  284. var hasMixedTypes = structure.HasWords && structure.HasSingleChars;
  285. // 根据混合特点选择更精细的策略
  286. string strategy;
  287. if (hasMixedTypes)
  288. {
  289. if (isCharDominant && hasManyChars)
  290. {
  291. strategy = "char_optimized"; // 单字主导,优化单字搜索
  292. }
  293. else if (hasLongWords && !hasManyChars)
  294. {
  295. strategy = "word_focused"; // 词语为主,单字为辅
  296. }
  297. else
  298. {
  299. strategy = complexity >= 6 ? "strict_mixed" :
  300. complexity >= 3 ? "balanced_mixed" : "loose_mixed";
  301. }
  302. }
  303. else
  304. {
  305. strategy = complexity >= 7 ? "strict" :
  306. complexity >= 4 ? "balanced" : "loose";
  307. }
  308. // 处理词语部分
  309. if (structure.HasWords)
  310. {
  311. if (structure.Words.Count == 1)
  312. {
  313. // 单个词语
  314. var (wordCondition, wordParams) = BuildSingleWordCondition(
  315. structure.Words[0], searchFields, $"{prefix}word", config);
  316. if (!string.IsNullOrEmpty(wordCondition))
  317. {
  318. conditions.Add(wordCondition);
  319. parameters.AddRange(wordParams);
  320. }
  321. }
  322. else
  323. {
  324. // 多个词语
  325. var wordsText = string.Join(" ", structure.Words);
  326. // 根据策略动态调整词语匹配要求
  327. bool requireAllWords;
  328. if (strategy == "char_optimized" && isCharDominant)
  329. {
  330. // 单字主导时,放宽词语要求
  331. requireAllWords = false;
  332. }
  333. else
  334. {
  335. requireAllWords = strategy.Contains("strict") || config.RequireAllWords;
  336. }
  337. var (wordsCondition, wordsParams) = BuildMultiWordCondition(
  338. wordsText, searchFields, $"{prefix}words", requireAllWords);
  339. if (!string.IsNullOrEmpty(wordsCondition))
  340. {
  341. conditions.Add(wordsCondition);
  342. parameters.AddRange(wordsParams);
  343. }
  344. }
  345. }
  346. // 处理单字部分 - 优化策略
  347. if (structure.HasSingleChars)
  348. {
  349. var charsText = string.Join("", structure.SingleChars);
  350. // 根据策略动态调整单字匹配要求
  351. bool requireAllChars;
  352. string charStrategy;
  353. if (strategy == "char_optimized")
  354. {
  355. // 单字优化策略:多个单字时放宽要求
  356. requireAllChars = structure.SingleChars.Count <= 3; // 3个及以下单字要求全部匹配
  357. charStrategy = "optimized";
  358. }
  359. else if (strategy.Contains("loose"))
  360. {
  361. requireAllChars = false;
  362. charStrategy = "loose";
  363. }
  364. else if (strategy.Contains("strict"))
  365. {
  366. requireAllChars = true;
  367. charStrategy = "strict";
  368. }
  369. else
  370. {
  371. requireAllChars = strategy.Contains("balanced") && structure.SingleChars.Count <= 2;
  372. charStrategy = "balanced";
  373. }
  374. var (charsCondition, charsParams) = BuildEnhancedCharCondition(
  375. charsText, searchFields, $"{prefix}chars",
  376. requireAllChars, charStrategy);
  377. if (!string.IsNullOrEmpty(charsCondition))
  378. {
  379. conditions.Add(charsCondition);
  380. parameters.AddRange(charsParams);
  381. }
  382. }
  383. // 构建最终条件
  384. if (!conditions.Any())
  385. return (string.Empty, parameters);
  386. string finalCondition = BuildMixedConditionByStrategy(
  387. conditions, structure, strategy);
  388. return (finalCondition, parameters);
  389. }
  390. /// <summary>
  391. /// 构建智能拆分条件(针对长词如"重庆科学")
  392. /// </summary>
  393. private static (string, List<SugarParameter>) BuildSmartSplitCondition(
  394. SearchStructure structure,
  395. List<string> searchFields,
  396. string prefix,
  397. SearchConfig config,
  398. List<string> longWords)
  399. {
  400. var parameters = new List<SugarParameter>();
  401. var conditions = new List<string>();
  402. // 处理所有长词(需要拆分的词)
  403. foreach (var longWord in longWords)
  404. {
  405. if (IsSplittableCombinationWord(longWord))
  406. {
  407. // 为每个长词构建多层次条件
  408. var (wordConditions, wordParams) = BuildMultiLevelConditionForWord(
  409. longWord, searchFields, $"{prefix}word_{longWord.GetHashCode()}", config);
  410. conditions.AddRange(wordConditions);
  411. parameters.AddRange(wordParams);
  412. }
  413. else
  414. {
  415. // 普通长词,直接搜索
  416. var (wordCondition, wordParams) = BuildSingleWordCondition(
  417. longWord, searchFields, $"{prefix}long_{parameters.Count}", config);
  418. if (!string.IsNullOrEmpty(wordCondition))
  419. {
  420. conditions.Add(wordCondition);
  421. parameters.AddRange(wordParams);
  422. }
  423. }
  424. }
  425. // 处理剩余的非长词
  426. var normalWords = structure.Words.Where(w => w.Length < config.MinSplitLength).ToList();
  427. if (normalWords.Any())
  428. {
  429. var wordsText = string.Join(" ", normalWords);
  430. var (normalCondition, normalParams) = BuildMultiWordCondition(
  431. wordsText, searchFields, $"{prefix}normal", config.RequireAllWords);
  432. if (!string.IsNullOrEmpty(normalCondition))
  433. {
  434. conditions.Add(normalCondition);
  435. parameters.AddRange(normalParams);
  436. }
  437. }
  438. // 处理原有的单字
  439. if (structure.SingleChars.Any())
  440. {
  441. var charsText = string.Join("", structure.SingleChars);
  442. var (charCondition, charParams) = BuildSingleCharCondition(
  443. charsText, searchFields, $"{prefix}single", false);
  444. if (!string.IsNullOrEmpty(charCondition))
  445. {
  446. conditions.Add(charCondition);
  447. parameters.AddRange(charParams);
  448. }
  449. }
  450. // 构建最终条件:使用层次化OR连接
  451. if (conditions.Count == 0)
  452. return (string.Empty, parameters);
  453. if (conditions.Count == 1)
  454. return (conditions[0], parameters);
  455. // 对条件进行分组和优先级排序
  456. var groupedConditions = GroupConditionsByPriority(conditions, longWords);
  457. return (BuildHierarchicalCondition(groupedConditions), parameters);
  458. }
  459. /// <summary>
  460. /// 为单个长词构建多层次条件
  461. /// </summary>
  462. private static (List<string> Conditions, List<SugarParameter> Parameters) BuildMultiLevelConditionForWord(
  463. string longWord,
  464. List<string> searchFields,
  465. string prefix,
  466. SearchConfig config)
  467. {
  468. var conditions = new List<string>();
  469. var parameters = new List<SugarParameter>();
  470. // 第一层:完整词匹配(最高优先级)
  471. var (fullCondition, fullParams) = BuildSingleWordCondition(
  472. longWord, searchFields, $"{prefix}_full", config);
  473. if (!string.IsNullOrEmpty(fullCondition))
  474. {
  475. conditions.Add(fullCondition);
  476. parameters.AddRange(fullParams);
  477. }
  478. // 第二层:智能拆分成子词
  479. var subWords = SplitCombinationWord(longWord);
  480. if (subWords.Count >= 2)
  481. {
  482. var (splitCondition, splitParams) = BuildMultiWordCondition(
  483. string.Join(" ", subWords),
  484. searchFields,
  485. $"{prefix}_split",
  486. config.RequireAllWordsForSplit);
  487. if (!string.IsNullOrEmpty(splitCondition))
  488. {
  489. conditions.Add($"({splitCondition})"); // 用括号包裹
  490. parameters.AddRange(splitParams);
  491. }
  492. }
  493. // 第三层:拆分成单字作为备选
  494. var chars = longWord.ToCharArray().Select(c => c.ToString()).ToList();
  495. var (charCondition, charParams) = BuildSingleCharCondition(
  496. string.Join("", chars),
  497. searchFields,
  498. $"{prefix}_chars",
  499. config.RequireAllCharsForSplit);
  500. if (!string.IsNullOrEmpty(charCondition))
  501. {
  502. conditions.Add($"({charCondition})");
  503. parameters.AddRange(charParams);
  504. }
  505. // 如果有多层条件,构建OR连接
  506. if (conditions.Count > 1)
  507. {
  508. var combinedCondition = $"({string.Join(" OR ", conditions)})";
  509. return (new List<string> { combinedCondition }, parameters);
  510. }
  511. return (conditions, parameters);
  512. }
  513. /// <summary>
  514. /// 构建智能拆分条件(用于分阶段搜索)
  515. /// </summary>
  516. private static (string, List<SugarParameter>) BuildSmartSplitConditionForWord(
  517. string longWord,
  518. List<string> searchFields,
  519. string prefix,
  520. SearchConfig config)
  521. {
  522. var (conditions, parameters) = BuildMultiLevelConditionForWord(longWord, searchFields, prefix, config);
  523. if (conditions.Any())
  524. {
  525. return (conditions[0], parameters);
  526. }
  527. return (string.Empty, new List<SugarParameter>());
  528. }
  529. /// <summary>
  530. /// 词语和单字组合条件
  531. /// </summary>
  532. private static (string, List<SugarParameter>) BuildWordsAndCharsCondition(
  533. SearchStructure structure,
  534. List<string> searchFields,
  535. string prefix,
  536. SearchConfig config)
  537. {
  538. var parameters = new List<SugarParameter>();
  539. var conditions = new List<string>();
  540. // 词语部分(必须满足)
  541. if (structure.Words.Any())
  542. {
  543. var wordsText = string.Join(" ", structure.Words);
  544. var (wordsCondition, wordsParams) = BuildMultiWordCondition(
  545. wordsText, searchFields, $"{prefix}w", true);
  546. if (!string.IsNullOrEmpty(wordsCondition))
  547. {
  548. conditions.Add(wordsCondition);
  549. parameters.AddRange(wordsParams);
  550. }
  551. }
  552. // 单字部分(作为增强条件)
  553. if (structure.SingleChars.Any())
  554. {
  555. var charsText = string.Join("", structure.SingleChars);
  556. var (charsCondition, charsParams) = BuildSingleCharCondition(
  557. charsText, searchFields, $"{prefix}c", false);
  558. if (!string.IsNullOrEmpty(charsCondition))
  559. {
  560. // 单字作为加分项,用OR连接
  561. conditions.Add($"({charsCondition})");
  562. parameters.AddRange(charsParams);
  563. }
  564. }
  565. if (!conditions.Any())
  566. return (string.Empty, parameters);
  567. // 构建条件:词语必须满足,单字作为增强
  568. string finalCondition = conditions.Count > 1
  569. ? $"({conditions[0]} AND ({string.Join(" OR ", conditions.Skip(1))}))"
  570. : conditions[0];
  571. return (finalCondition, parameters);
  572. }
  573. /// <summary>
  574. /// 仅词语条件
  575. /// </summary>
  576. private static (string, List<SugarParameter>) BuildWordsOnlyCondition(
  577. SearchStructure structure,
  578. List<string> searchFields,
  579. string prefix,
  580. SearchConfig config)
  581. {
  582. if (!structure.Words.Any())
  583. return (string.Empty, new List<SugarParameter>());
  584. var wordsText = string.Join(" ", structure.Words);
  585. if (structure.Words.Count == 1)
  586. {
  587. return BuildSingleWordCondition(wordsText, searchFields, prefix, config);
  588. }
  589. else
  590. {
  591. return BuildMultiWordCondition(wordsText, searchFields, prefix, config.RequireAllWords);
  592. }
  593. }
  594. /// <summary>
  595. /// 仅单字条件
  596. /// </summary>
  597. private static (string, List<SugarParameter>) BuildCharsOnlyCondition(
  598. SearchStructure structure,
  599. List<string> searchFields,
  600. string prefix,
  601. SearchConfig config)
  602. {
  603. if (!structure.SingleChars.Any())
  604. return (string.Empty, new List<SugarParameter>());
  605. var charsText = string.Join("", structure.SingleChars);
  606. return BuildSingleCharCondition(charsText, searchFields, prefix, config.RequireAllChars);
  607. }
  608. #endregion
  609. #region 基础条件构建方法
  610. /// <summary>
  611. /// 构建单词条查询条件
  612. /// </summary>
  613. public static (string Condition, List<SugarParameter> Parameters) BuildSingleWordCondition(
  614. string searchTerm,
  615. List<string> searchFields,
  616. string prefix,
  617. SearchConfig config = null)
  618. {
  619. config ??= new SearchConfig();
  620. var parameters = new List<SugarParameter>();
  621. var fieldConditions = new List<string>();
  622. var paramName = $"{prefix}word";
  623. foreach (var field in searchFields)
  624. {
  625. fieldConditions.Add($"{field} LIKE @{paramName}");
  626. }
  627. string condition = $"({string.Join(" OR ", fieldConditions)})";
  628. parameters.Add(new SugarParameter($"@{paramName}", $"%{searchTerm}%"));
  629. return (condition, parameters);
  630. }
  631. /// <summary>
  632. /// 构建多词条查询条件
  633. /// </summary>
  634. public static (string Condition, List<SugarParameter> Parameters) BuildMultiWordCondition(
  635. string searchTerm,
  636. List<string> searchFields,
  637. string prefix,
  638. bool requireAll = true)
  639. {
  640. var parameters = new List<SugarParameter>();
  641. var separators = new[] { ' ', ',', '、', ',', ';', ';' };
  642. var words = searchTerm.Split(separators, StringSplitOptions.RemoveEmptyEntries)
  643. .Select(w => w.Trim())
  644. .Where(w => !string.IsNullOrEmpty(w))
  645. .Distinct()
  646. .ToList();
  647. if (!words.Any())
  648. return (string.Empty, parameters);
  649. var wordConditions = new List<string>();
  650. foreach (var word in words)
  651. {
  652. var fieldConditions = new List<string>();
  653. var paramName = $"{prefix}word_{parameters.Count}";
  654. foreach (var field in searchFields)
  655. {
  656. fieldConditions.Add($"{field} LIKE @{paramName}");
  657. }
  658. wordConditions.Add($"({string.Join(" OR ", fieldConditions)})");
  659. parameters.Add(new SugarParameter($"@{paramName}", $"%{word}%"));
  660. }
  661. // 构建条件
  662. string condition;
  663. if (requireAll && words.Count > 1)
  664. {
  665. condition = $"({string.Join(" AND ", wordConditions)})";
  666. }
  667. else if (!requireAll && words.Count > 1)
  668. {
  669. condition = $"({string.Join(" OR ", wordConditions)})";
  670. }
  671. else
  672. {
  673. condition = wordConditions.FirstOrDefault() ?? "";
  674. }
  675. return (condition, parameters);
  676. }
  677. /// <summary>
  678. /// 构建单字查询条件
  679. /// </summary>
  680. public static (string Condition, List<SugarParameter> Parameters) BuildSingleCharCondition(
  681. string searchTerm,
  682. List<string> searchFields,
  683. string prefix,
  684. bool requireAll = true)
  685. {
  686. var parameters = new List<SugarParameter>();
  687. var cleanTerm = RemoveSeparators(searchTerm);
  688. var chars = cleanTerm.ToCharArray().Distinct().ToArray();
  689. if (chars.Length == 0)
  690. return (string.Empty, parameters);
  691. var charConditions = new List<string>();
  692. foreach (char c in chars)
  693. {
  694. var fieldConditions = new List<string>();
  695. var paramName = $"{prefix}char_{parameters.Count}";
  696. foreach (var field in searchFields)
  697. {
  698. fieldConditions.Add($"{field} LIKE @{paramName}");
  699. }
  700. charConditions.Add($"({string.Join(" OR ", fieldConditions)})");
  701. parameters.Add(new SugarParameter($"@{paramName}", $"%{c}%"));
  702. }
  703. // 构建条件
  704. string condition;
  705. if (requireAll && chars.Length > 1)
  706. {
  707. condition = $"({string.Join(" AND ", charConditions)})";
  708. }
  709. else if (!requireAll && chars.Length > 1)
  710. {
  711. condition = $"({string.Join(" OR ", charConditions)})";
  712. }
  713. else
  714. {
  715. condition = charConditions.FirstOrDefault() ?? "";
  716. }
  717. return (condition, parameters);
  718. }
  719. /// <summary>
  720. /// 构建增强的单字搜索条件(支持不同策略)
  721. /// </summary>
  722. private static (string, List<SugarParameter>) BuildEnhancedCharCondition(
  723. string searchTerm,
  724. List<string> searchFields,
  725. string prefix,
  726. bool requireAll,
  727. string strategy)
  728. {
  729. var parameters = new List<SugarParameter>();
  730. var cleanTerm = RemoveSeparators(searchTerm);
  731. var chars = cleanTerm.ToCharArray().Distinct().ToArray();
  732. if (chars.Length == 0)
  733. return (string.Empty, parameters);
  734. // 根据策略调整处理方式
  735. var charConditions = new List<string>();
  736. foreach (char c in chars)
  737. {
  738. var fieldConditions = new List<string>();
  739. var paramName = $"{prefix}char_{parameters.Count}";
  740. foreach (var field in searchFields)
  741. {
  742. // 根据不同策略使用不同的匹配方式
  743. if (strategy == "optimized" && IsChineseCharacter(c))
  744. {
  745. // 优化策略:中文单字更宽松
  746. fieldConditions.Add($"({field} LIKE @{paramName} OR {field} LIKE @{paramName}_prefix)");
  747. parameters.Add(new SugarParameter($"@{paramName}", $"%{c}%"));
  748. parameters.Add(new SugarParameter($"@{paramName}_prefix", $"{c}%"));
  749. }
  750. else
  751. {
  752. fieldConditions.Add($"{field} LIKE @{paramName}");
  753. parameters.Add(new SugarParameter($"@{paramName}", $"%{c}%"));
  754. }
  755. }
  756. charConditions.Add($"({string.Join(" OR ", fieldConditions)})");
  757. }
  758. // 根据策略构建条件
  759. string condition;
  760. if (requireAll && chars.Length > 1)
  761. {
  762. // 必须包含所有单字
  763. condition = $"({string.Join(" AND ", charConditions)})";
  764. }
  765. else if (!requireAll && chars.Length > 1)
  766. {
  767. // 任意一个单字即可
  768. condition = $"({string.Join(" OR ", charConditions)})";
  769. }
  770. else if (strategy == "optimized" && chars.Length > 2)
  771. {
  772. // 优化策略:3个以上单字时,至少匹配一半
  773. int requiredCount = (int)Math.Ceiling(chars.Length / 2.0);
  774. var orConditions = new List<string>();
  775. // 生成所有可能的组合(限制数量避免爆炸)
  776. var combinations = GetCharCombinations(charConditions, chars.Length, requiredCount, 5);
  777. foreach (var combination in combinations)
  778. {
  779. orConditions.Add($"({string.Join(" AND ", combination)})");
  780. }
  781. condition = orConditions.Any() ? $"({string.Join(" OR ", orConditions)})" : charConditions.FirstOrDefault() ?? "";
  782. }
  783. else
  784. {
  785. condition = charConditions.FirstOrDefault() ?? "";
  786. }
  787. return (condition, parameters);
  788. }
  789. /// <summary>
  790. /// 构建精确匹配条件
  791. /// </summary>
  792. public static (string Condition, List<SugarParameter> Parameters) BuildExactCondition(
  793. string searchTerm,
  794. List<string> searchFields,
  795. string prefix)
  796. {
  797. var parameters = new List<SugarParameter>();
  798. var fieldConditions = new List<string>();
  799. var paramName = $"{prefix}exact";
  800. foreach (var field in searchFields)
  801. {
  802. fieldConditions.Add($"{field} = @{paramName}");
  803. }
  804. string condition = $"({string.Join(" OR ", fieldConditions)})";
  805. parameters.Add(new SugarParameter($"@{paramName}", searchTerm.Trim()));
  806. return (condition, parameters);
  807. }
  808. /// <summary>
  809. /// 构建开头匹配条件
  810. /// </summary>
  811. public static (string Condition, List<SugarParameter> Parameters) BuildPrefixCondition(
  812. string searchTerm,
  813. List<string> searchFields,
  814. string prefix)
  815. {
  816. var parameters = new List<SugarParameter>();
  817. var fieldConditions = new List<string>();
  818. var paramName = $"{prefix}prefix";
  819. foreach (var field in searchFields)
  820. {
  821. fieldConditions.Add($"{field} LIKE @{paramName}");
  822. }
  823. string condition = $"({string.Join(" OR ", fieldConditions)})";
  824. parameters.Add(new SugarParameter($"@{paramName}", $"{searchTerm}%"));
  825. return (condition, parameters);
  826. }
  827. /// <summary>
  828. /// 构建结尾匹配条件
  829. /// </summary>
  830. public static (string Condition, List<SugarParameter> Parameters) BuildSuffixCondition(
  831. string searchTerm,
  832. List<string> searchFields,
  833. string prefix)
  834. {
  835. var parameters = new List<SugarParameter>();
  836. var fieldConditions = new List<string>();
  837. var paramName = $"{prefix}suffix";
  838. foreach (var field in searchFields)
  839. {
  840. fieldConditions.Add($"{field} LIKE @{paramName}");
  841. }
  842. string condition = $"({string.Join(" OR ", fieldConditions)})";
  843. parameters.Add(new SugarParameter($"@{paramName}", $"%{searchTerm}"));
  844. return (condition, parameters);
  845. }
  846. #endregion
  847. #region 智能拆分方法
  848. /// <summary>
  849. /// 判断是否为可拆分的组合词
  850. /// </summary>
  851. private static bool IsSplittableCombinationWord(string word)
  852. {
  853. if (word.Length < 4)
  854. return false;
  855. // 检查是否由常见的地名+名词等组成
  856. var commonPatterns = new HashSet<string>
  857. {
  858. "重庆", "北京", "上海", "广州", "深圳", "天津", "武汉", "成都", "杭州", "南京",
  859. "科学", "技术", "研究", "发展", "教育", "培训", "管理", "系统", "工程", "设计",
  860. "大学", "学院", "学校", "公司", "集团", "中心", "研究所", "实验室", "医院", "银行"
  861. };
  862. // 尝试找到明显的组合边界
  863. for (int i = 2; i <= word.Length - 2; i++)
  864. {
  865. var part1 = word.Substring(0, i);
  866. var part2 = word.Substring(i);
  867. if (commonPatterns.Contains(part1) || commonPatterns.Contains(part2))
  868. return true;
  869. // 检查是否都是常见词汇
  870. if (IsCommonWord(part1) && IsCommonWord(part2))
  871. return true;
  872. }
  873. return false;
  874. }
  875. /// <summary>
  876. /// 拆分组合词
  877. /// </summary>
  878. private static List<string> SplitCombinationWord(string word)
  879. {
  880. var result = new List<string>();
  881. if (word.Length <= 2)
  882. {
  883. result.Add(word);
  884. return result;
  885. }
  886. // 优先按2+2模式拆分(如"重庆科学" -> "重庆", "科学")
  887. if (word.Length == 4)
  888. {
  889. result.Add(word.Substring(0, 2));
  890. result.Add(word.Substring(2, 2));
  891. return result;
  892. }
  893. // 尝试按常见模式拆分
  894. var commonTwoCharWords = new HashSet<string>
  895. {
  896. "重庆", "北京", "上海", "广州", "深圳", "天津", "武汉", "成都", "杭州", "南京",
  897. "科学", "技术", "研究", "发展", "教育", "培训", "管理", "系统", "工程", "设计",
  898. "大学", "学院", "学校", "公司", "集团", "中心", "医院", "银行", "保险", "证券",
  899. "软件", "硬件", "网络", "数据", "信息", "智能", "数字", "电子", "通信", "互联网"
  900. };
  901. // 检查前缀
  902. for (int i = 2; i <= Math.Min(4, word.Length - 2); i++)
  903. {
  904. var prefix = word.Substring(0, i);
  905. var suffix = word.Substring(i);
  906. if (commonTwoCharWords.Contains(prefix) ||
  907. (i == 2 && IsLikelyWord(prefix)))
  908. {
  909. result.Add(prefix);
  910. // 递归拆分剩余部分
  911. if (suffix.Length >= 2)
  912. {
  913. var subSplits = SplitCombinationWord(suffix);
  914. result.AddRange(subSplits);
  915. }
  916. else
  917. {
  918. result.Add(suffix);
  919. }
  920. return result;
  921. }
  922. }
  923. // 无法智能拆分,按固定长度拆分
  924. int splitLength = word.Length >= 6 ? 3 : 2;
  925. for (int i = 0; i < word.Length; i += splitLength)
  926. {
  927. var part = word.Substring(i, Math.Min(splitLength, word.Length - i));
  928. result.Add(part);
  929. }
  930. return result;
  931. }
  932. /// <summary>
  933. /// 检查是否可能是常见词汇
  934. /// </summary>
  935. private static bool IsLikelyWord(string word)
  936. {
  937. if (word.Length != 2) return false;
  938. // 检查是否为常见的中文二字词
  939. // 这里可以扩展更复杂的判断逻辑
  940. return IsChineseCharacter(word[0]) && IsChineseCharacter(word[1]);
  941. }
  942. /// <summary>
  943. /// 检查是否是常见词汇
  944. /// </summary>
  945. private static bool IsCommonWord(string word)
  946. {
  947. // 这里可以扩展更复杂的常见词判断逻辑
  948. // 例如从词典加载、使用统计等
  949. var commonWords = new HashSet<string>
  950. {
  951. "中国", "国家", "人民", "社会", "经济", "文化", "教育", "科技", "发展",
  952. "企业", "公司", "市场", "产品", "服务", "技术", "管理", "工作", "生活"
  953. };
  954. return commonWords.Contains(word) ||
  955. (word.Length == 2 && IsChineseCharacter(word[0]) && IsChineseCharacter(word[1]));
  956. }
  957. /// <summary>
  958. /// 按优先级分组条件
  959. /// </summary>
  960. private static Dictionary<string, List<string>> GroupConditionsByPriority(
  961. List<string> conditions, List<string> longWords)
  962. {
  963. var groups = new Dictionary<string, List<string>>
  964. {
  965. ["original"] = new List<string>(), // 原词条件
  966. ["split"] = new List<string>(), // 拆分子词条件
  967. ["chars"] = new List<string>(), // 单字条件
  968. ["normal"] = new List<string>() // 普通词条件
  969. };
  970. // 简单分组逻辑,实际使用中可能需要更复杂的判断
  971. int longWordCount = longWords.Count;
  972. int currentIndex = 0;
  973. // 原词条件(每个长词一个)
  974. for (int i = 0; i < longWordCount && currentIndex < conditions.Count; i++)
  975. {
  976. groups["original"].Add(conditions[currentIndex]);
  977. currentIndex++;
  978. }
  979. // 拆分子词条件
  980. for (int i = 0; i < longWordCount && currentIndex < conditions.Count; i++)
  981. {
  982. groups["split"].Add(conditions[currentIndex]);
  983. currentIndex++;
  984. }
  985. // 单字条件
  986. for (int i = 0; i < longWordCount && currentIndex < conditions.Count; i++)
  987. {
  988. groups["chars"].Add(conditions[currentIndex]);
  989. currentIndex++;
  990. }
  991. // 剩余的都是普通词条件
  992. while (currentIndex < conditions.Count)
  993. {
  994. groups["normal"].Add(conditions[currentIndex]);
  995. currentIndex++;
  996. }
  997. return groups;
  998. }
  999. /// <summary>
  1000. /// 构建层次化条件
  1001. /// </summary>
  1002. private static string BuildHierarchicalCondition(Dictionary<string, List<string>> groups)
  1003. {
  1004. var parts = new List<string>();
  1005. // 第一层:原词匹配(最高优先级)
  1006. if (groups["original"].Any())
  1007. {
  1008. parts.Add($"({string.Join(" OR ", groups["original"])})");
  1009. }
  1010. // 第二层:拆分子词匹配
  1011. if (groups["split"].Any())
  1012. {
  1013. var splitCondition = groups["split"].Count == 1
  1014. ? groups["split"][0]
  1015. : $"({string.Join(" AND ", groups["split"])})";
  1016. parts.Add($"({splitCondition})");
  1017. }
  1018. // 普通词条件(插入到合适位置)
  1019. if (groups["normal"].Any())
  1020. {
  1021. var normalCondition = groups["normal"].Count == 1
  1022. ? groups["normal"][0]
  1023. : $"({string.Join(" AND ", groups["normal"])})";
  1024. // 如果已经有原词条件,放在第二层;否则放在第一层
  1025. if (parts.Count >= 1)
  1026. parts.Insert(1, normalCondition);
  1027. else
  1028. parts.Add(normalCondition);
  1029. }
  1030. // 第三层:单字匹配(最低优先级)
  1031. if (groups["chars"].Any())
  1032. {
  1033. var charCondition = groups["chars"].Count == 1
  1034. ? groups["chars"][0]
  1035. : $"({string.Join(" OR ", groups["chars"])})";
  1036. parts.Add($"({charCondition})");
  1037. }
  1038. if (parts.Count == 0)
  1039. return string.Empty;
  1040. if (parts.Count == 1)
  1041. return parts[0];
  1042. // 构建层次化OR条件
  1043. return $"({string.Join(" OR ", parts)})";
  1044. }
  1045. /// <summary>
  1046. /// 根据策略构建混合条件
  1047. /// </summary>
  1048. private static string BuildMixedConditionByStrategy(
  1049. List<string> conditions,
  1050. SearchStructure structure,
  1051. string strategy)
  1052. {
  1053. if (conditions.Count == 1)
  1054. return conditions[0];
  1055. switch (strategy)
  1056. {
  1057. case "char_optimized":
  1058. // 单字优化策略:词语作为主要条件,单字作为增强
  1059. return BuildCharOptimizedCondition(conditions, structure);
  1060. case "word_focused":
  1061. // 词语聚焦策略:词语必须,单字作为加分
  1062. return BuildWordFocusedCondition(conditions, structure);
  1063. case "strict_mixed":
  1064. // 严格混合:所有条件必须满足
  1065. return BuildStrictCondition(conditions, structure);
  1066. case "balanced_mixed":
  1067. // 平衡混合:词语必须,单字推荐
  1068. return BuildBalancedCondition(conditions, structure);
  1069. case "loose_mixed":
  1070. // 宽松混合:任意条件满足即可
  1071. return BuildLooseCondition(conditions, structure);
  1072. default:
  1073. return BuildBalancedCondition(conditions, structure);
  1074. }
  1075. }
  1076. /// <summary>
  1077. /// 构建单字优化条件
  1078. /// </summary>
  1079. private static string BuildCharOptimizedCondition(List<string> conditions, SearchStructure structure)
  1080. {
  1081. var wordConditions = conditions
  1082. .Where((c, i) => i < structure.Words.Count)
  1083. .ToList();
  1084. var charConditions = conditions
  1085. .Skip(structure.Words.Count)
  1086. .ToList();
  1087. if (wordConditions.Any() && charConditions.Any())
  1088. {
  1089. // 词语必须,单字作为重要补充(至少匹配一定比例)
  1090. if (charConditions.Count > 2)
  1091. {
  1092. // 多个单字时,要求至少匹配一半
  1093. int requiredChars = (int)Math.Ceiling(charConditions.Count / 2.0);
  1094. var charCombinations = GetConditionCombinations(charConditions, requiredChars, 3);
  1095. if (charCombinations.Any())
  1096. {
  1097. var charCombinationStrings = charCombinations
  1098. .Select(comb => $"({string.Join(" AND ", comb)})")
  1099. .ToList();
  1100. return $"({string.Join(" AND ", wordConditions)} AND ({string.Join(" OR ", charCombinationStrings)}))";
  1101. }
  1102. }
  1103. return $"({string.Join(" AND ", wordConditions)} AND ({string.Join(" OR ", charConditions)}))";
  1104. }
  1105. else if (charConditions.Any())
  1106. {
  1107. // 只有单字时,至少匹配一半
  1108. if (charConditions.Count > 1)
  1109. {
  1110. int requiredChars = (int)Math.Ceiling(charConditions.Count / 2.0);
  1111. var combinations = GetConditionCombinations(charConditions, requiredChars, 3);
  1112. if (combinations.Any())
  1113. {
  1114. var combinationStrings = combinations
  1115. .Select(comb => $"({string.Join(" AND ", comb)})")
  1116. .ToList();
  1117. return $"({string.Join(" OR ", combinationStrings)})";
  1118. }
  1119. }
  1120. return $"({string.Join(" OR ", charConditions)})";
  1121. }
  1122. return conditions.FirstOrDefault() ?? "";
  1123. }
  1124. /// <summary>
  1125. /// 构建词语聚焦条件
  1126. /// </summary>
  1127. private static string BuildWordFocusedCondition(List<string> conditions, SearchStructure structure)
  1128. {
  1129. var wordConditions = conditions
  1130. .Where((c, i) => i < structure.Words.Count)
  1131. .ToList();
  1132. var charConditions = conditions
  1133. .Skip(structure.Words.Count)
  1134. .ToList();
  1135. if (wordConditions.Any() && charConditions.Any())
  1136. {
  1137. // 词语必须满足,单字作为加分项(OR连接)
  1138. return $"({string.Join(" AND ", wordConditions)} AND ({string.Join(" OR ", charConditions)}))";
  1139. }
  1140. return $"({string.Join(" AND ", conditions)})";
  1141. }
  1142. /// <summary>
  1143. /// 获取条件组合(限制数量避免爆炸)
  1144. /// </summary>
  1145. private static List<List<string>> GetConditionCombinations(
  1146. List<string> conditions,
  1147. int requiredCount,
  1148. int maxCombinations = 10)
  1149. {
  1150. var result = new List<List<string>>();
  1151. if (conditions.Count <= requiredCount || conditions.Count <= 1)
  1152. {
  1153. result.Add(conditions);
  1154. return result;
  1155. }
  1156. // 生成C(n, requiredCount)组合,但限制数量
  1157. var indices = Enumerable.Range(0, conditions.Count).ToArray();
  1158. var combinations = GetCombinations(indices, requiredCount, maxCombinations);
  1159. foreach (var combination in combinations)
  1160. {
  1161. var selectedConditions = combination
  1162. .Select(idx => conditions[idx])
  1163. .ToList();
  1164. result.Add(selectedConditions);
  1165. }
  1166. return result;
  1167. }
  1168. /// <summary>
  1169. /// 获取字符条件组合
  1170. /// </summary>
  1171. private static List<List<string>> GetCharCombinations(
  1172. List<string> charConditions,
  1173. int totalChars,
  1174. int requiredCount,
  1175. int maxCombinations)
  1176. {
  1177. return GetConditionCombinations(charConditions, requiredCount, maxCombinations);
  1178. }
  1179. /// <summary>
  1180. /// 获取索引组合(算法)
  1181. /// </summary>
  1182. private static List<List<int>> GetCombinations(int[] indices, int k, int maxCombinations)
  1183. {
  1184. var result = new List<List<int>>();
  1185. var combination = new List<int>();
  1186. void Backtrack(int start)
  1187. {
  1188. if (result.Count >= maxCombinations)
  1189. return;
  1190. if (combination.Count == k)
  1191. {
  1192. result.Add(new List<int>(combination));
  1193. return;
  1194. }
  1195. for (int i = start; i < indices.Length; i++)
  1196. {
  1197. combination.Add(indices[i]);
  1198. Backtrack(i + 1);
  1199. combination.RemoveAt(combination.Count - 1);
  1200. if (result.Count >= maxCombinations)
  1201. break;
  1202. }
  1203. }
  1204. Backtrack(0);
  1205. return result;
  1206. }
  1207. #endregion
  1208. #region 分析工具方法
  1209. /// <summary>
  1210. /// 分析搜索词结构
  1211. /// </summary>
  1212. public static SearchStructure AnalyzeSearchStructure(string searchTerm)
  1213. {
  1214. var structure = new SearchStructure
  1215. {
  1216. OriginalTerm = searchTerm,
  1217. CleanTerm = searchTerm.Trim(),
  1218. AnalyzedAt = DateTime.Now
  1219. };
  1220. // 移除分隔符获取所有字符
  1221. var cleanTerm = RemoveSeparators(searchTerm);
  1222. structure.AllCharacters = cleanTerm.ToCharArray().ToList();
  1223. // 分割成词条(保持原始分割)
  1224. var separators = new[] { ' ', ',', '、', ',', ';', ';', '|', '/', '\\' };
  1225. var rawSegments = searchTerm.Split(separators, StringSplitOptions.RemoveEmptyEntries)
  1226. .Select(s => s.Trim())
  1227. .Where(s => !string.IsNullOrEmpty(s))
  1228. .ToList();
  1229. structure.Segments = rawSegments;
  1230. // 分析每个段落的类型
  1231. foreach (var segment in rawSegments)
  1232. {
  1233. if (segment.Length == 1 && IsChineseCharacter(segment[0]))
  1234. {
  1235. structure.SingleChars.Add(segment);
  1236. structure.HasSingleChars = true;
  1237. }
  1238. else
  1239. {
  1240. structure.Words.Add(segment);
  1241. structure.HasWords = true;
  1242. }
  1243. }
  1244. // 设置组合类型
  1245. if (structure.HasWords && structure.HasSingleChars)
  1246. {
  1247. structure.CombinationType = CombinationType.WordsAndChars;
  1248. structure.Notes = "混合查询:包含词语和单字";
  1249. }
  1250. else if (structure.HasWords && !structure.HasSingleChars)
  1251. {
  1252. structure.CombinationType = CombinationType.WordsOnly;
  1253. structure.Notes = structure.Words.Count > 1 ? "多词查询" : "单词查询";
  1254. }
  1255. else if (!structure.HasWords && structure.HasSingleChars)
  1256. {
  1257. structure.CombinationType = CombinationType.CharsOnly;
  1258. structure.Notes = structure.SingleChars.Count > 1 ? "多字查询" : "单字查询";
  1259. }
  1260. else
  1261. {
  1262. structure.CombinationType = CombinationType.Unknown;
  1263. structure.Notes = "无法识别的查询类型";
  1264. }
  1265. // 计算复杂度评分
  1266. structure.ComplexityScore = CalculateComplexityScore(structure);
  1267. return structure;
  1268. }
  1269. /// <summary>
  1270. /// 计算搜索复杂度评分
  1271. /// </summary>
  1272. private static int CalculateComplexityScore(SearchStructure structure)
  1273. {
  1274. int score = 0;
  1275. // 词条数量(1-5分)
  1276. int segmentCount = structure.Words.Count + structure.SingleChars.Count;
  1277. score += Math.Min(segmentCount, 5);
  1278. // 是否混合类型(加3分)
  1279. if (structure.HasWords && structure.HasSingleChars)
  1280. score += 3;
  1281. // 单字数量(每3个加1分,最多2分)
  1282. if (structure.SingleChars.Count >= 3)
  1283. score += Math.Min(structure.SingleChars.Count / 3, 2);
  1284. // 词语平均长度(长词加2分)
  1285. if (structure.Words.Any())
  1286. {
  1287. double avgLength = structure.Words.Average(w => w.Length);
  1288. if (avgLength > 4) score += 2;
  1289. }
  1290. // 总字符数(10个以上加1分)
  1291. if (structure.AllCharacters.Count > 10)
  1292. score += 1;
  1293. return Math.Min(score, 10); // 最高10分
  1294. }
  1295. /// <summary>
  1296. /// 获取搜索策略
  1297. /// </summary>
  1298. private static MatchStrategy GetMatchStrategy(SearchStructure structure, SearchConfig config)
  1299. {
  1300. var complexity = CalculateComplexityScore(structure);
  1301. return new MatchStrategy
  1302. {
  1303. StrategyName = complexity >= 7 ? "strict" :
  1304. complexity >= 4 ? "balanced" : "loose",
  1305. ComplexityScore = complexity,
  1306. UseExactMatch = config.EnableExactMatch && structure.Words.Any(w => w.Length >= 3),
  1307. UsePrefixMatch = config.EnablePrefixMatch,
  1308. UseFuzzySearch = config.EnableFuzzySearch,
  1309. UseCharSearch = config.EnableCharSearch && structure.SingleChars.Any(),
  1310. UseSmartSplitting = config.EnableSmartSplitting && structure.Words.Any(w => w.Length >= config.MinSplitLength),
  1311. RecommendedPageSize = complexity >= 7 ? 10 : 20
  1312. };
  1313. }
  1314. /// <summary>
  1315. /// 获取计分规则
  1316. /// </summary>
  1317. private static List<ScoringRule> GetScoringRules(SearchStructure structure, SearchConfig config)
  1318. {
  1319. var rules = new List<ScoringRule>();
  1320. // 词语匹配规则
  1321. foreach (var word in structure.Words)
  1322. {
  1323. rules.Add(new ScoringRule
  1324. {
  1325. Type = "word",
  1326. Value = word,
  1327. BaseScore = 10,
  1328. Bonus = word.Length >= 3 ? 5 : 2,
  1329. Multiplier = 1.0,
  1330. Condition = $"包含词语'{word}'"
  1331. });
  1332. }
  1333. // 单字匹配规则
  1334. foreach (var ch in structure.SingleChars)
  1335. {
  1336. rules.Add(new ScoringRule
  1337. {
  1338. Type = "char",
  1339. Value = ch,
  1340. BaseScore = 3,
  1341. Bonus = 1,
  1342. Multiplier = 1.0,
  1343. Condition = $"包含单字'{ch}'"
  1344. });
  1345. }
  1346. // 混合匹配奖励
  1347. if (structure.HasWords && structure.HasSingleChars)
  1348. {
  1349. rules.Add(new ScoringRule
  1350. {
  1351. Type = "combo",
  1352. Value = "words_and_chars",
  1353. BaseScore = 0,
  1354. Bonus = 8,
  1355. Multiplier = 1.2,
  1356. Condition = "同时匹配词语和单字"
  1357. });
  1358. }
  1359. // 智能拆分奖励
  1360. if (config.EnableSmartSplitting && structure.Words.Any(w => w.Length >= config.MinSplitLength))
  1361. {
  1362. rules.Add(new ScoringRule
  1363. {
  1364. Type = "smart_split",
  1365. Value = "smart_split_match",
  1366. BaseScore = 0,
  1367. Bonus = 5,
  1368. Multiplier = 1.1,
  1369. Condition = "智能拆分匹配"
  1370. });
  1371. }
  1372. // 完全匹配奖励
  1373. rules.Add(new ScoringRule
  1374. {
  1375. Type = "exact",
  1376. Value = "exact_match",
  1377. BaseScore = 0,
  1378. Bonus = 15,
  1379. Multiplier = 1.5,
  1380. Condition = "完全匹配搜索词"
  1381. });
  1382. return rules;
  1383. }
  1384. /// <summary>
  1385. /// 获取需要提升权重的字段
  1386. /// </summary>
  1387. private static List<string> GetBoostFields(SearchStructure structure, List<string> searchFields)
  1388. {
  1389. var boostFields = new List<string>();
  1390. if (structure.HasWords && structure.Words.Any(w => w.Length >= 2))
  1391. {
  1392. // 长词语在名称字段中效果更好
  1393. boostFields.AddRange(searchFields.Where(f =>
  1394. f.Contains("name", StringComparison.OrdinalIgnoreCase) ||
  1395. f.Contains("title", StringComparison.OrdinalIgnoreCase) ||
  1396. f.Contains("subject", StringComparison.OrdinalIgnoreCase)));
  1397. }
  1398. if (structure.HasSingleChars)
  1399. {
  1400. // 单字在描述、内容字段中效果更好
  1401. boostFields.AddRange(searchFields.Where(f =>
  1402. f.Contains("desc", StringComparison.OrdinalIgnoreCase) ||
  1403. f.Contains("content", StringComparison.OrdinalIgnoreCase) ||
  1404. f.Contains("remark", StringComparison.OrdinalIgnoreCase) ||
  1405. f.Contains("note", StringComparison.OrdinalIgnoreCase)));
  1406. }
  1407. return boostFields.Distinct().ToList();
  1408. }
  1409. /// <summary>
  1410. /// 计算预期匹配数
  1411. /// </summary>
  1412. private static int CalculateExpectedMatches(SearchStructure structure, int fieldCount)
  1413. {
  1414. int baseMatches = structure.Words.Count + structure.SingleChars.Count;
  1415. int expected = baseMatches * fieldCount;
  1416. // 调整预期值
  1417. if (structure.CombinationType == CombinationType.WordsAndChars)
  1418. expected = (int)(expected * 1.5);
  1419. else if (structure.CombinationType == CombinationType.CharsOnly)
  1420. expected = (int)(expected * 0.7);
  1421. return Math.Max(expected, 1);
  1422. }
  1423. #endregion
  1424. #region 条件构建策略
  1425. /// <summary>
  1426. /// 构建严格条件(所有条件必须满足)
  1427. /// </summary>
  1428. private static string BuildStrictCondition(List<string> conditions, SearchStructure structure)
  1429. {
  1430. if (conditions.Count == 1)
  1431. return conditions[0];
  1432. // 词语条件在前,单字条件在后
  1433. var wordConditions = conditions
  1434. .Where((c, i) => i < structure.Words.Count)
  1435. .ToList();
  1436. var charConditions = conditions
  1437. .Skip(structure.Words.Count)
  1438. .ToList();
  1439. if (wordConditions.Any() && charConditions.Any())
  1440. {
  1441. return $"({string.Join(" AND ", wordConditions)} AND ({string.Join(" OR ", charConditions)}))";
  1442. }
  1443. return $"({string.Join(" AND ", conditions)})";
  1444. }
  1445. /// <summary>
  1446. /// 构建平衡条件
  1447. /// </summary>
  1448. private static string BuildBalancedCondition(List<string> conditions, SearchStructure structure)
  1449. {
  1450. if (conditions.Count == 1)
  1451. return conditions[0];
  1452. // 词语优先,单字作为增强
  1453. if (structure.HasWords && structure.HasSingleChars)
  1454. {
  1455. var wordCondition = conditions.First();
  1456. var charConditions = conditions.Skip(1).ToList();
  1457. return $"({wordCondition} AND ({string.Join(" OR ", charConditions)}))";
  1458. }
  1459. return $"({string.Join(" AND ", conditions)})";
  1460. }
  1461. /// <summary>
  1462. /// 构建宽松条件(提高召回率)
  1463. /// </summary>
  1464. private static string BuildLooseCondition(List<string> conditions, SearchStructure structure)
  1465. {
  1466. if (conditions.Count == 1)
  1467. return conditions[0];
  1468. // 所有条件OR连接
  1469. return $"({string.Join(" OR ", conditions)})";
  1470. }
  1471. #endregion
  1472. #region 实用工具方法
  1473. /// <summary>
  1474. /// 检查字符是否为中文字符
  1475. /// </summary>
  1476. public static bool IsChineseCharacter(char c)
  1477. {
  1478. // 基本汉字(0x4E00-0x9FFF)
  1479. if (c >= 0x4E00 && c <= 0x9FFF)
  1480. return true;
  1481. // 扩展A区(0x3400-0x4DBF)
  1482. if (c >= 0x3400 && c <= 0x4DBF)
  1483. return true;
  1484. // 基本汉字补充(0x9FA6-0x9FFF)
  1485. if (c >= 0x9FA6 && c <= 0x9FFF)
  1486. return true;
  1487. return false;
  1488. }
  1489. /// <summary>
  1490. /// 移除字符串中的分隔符
  1491. /// </summary>
  1492. private static string RemoveSeparators(string text)
  1493. {
  1494. if (string.IsNullOrEmpty(text))
  1495. return text;
  1496. var separators = new[] { ' ', ',', '、', ',', ';', ';', '-', '_', '.', '。', '·', '|', '/', '\\' };
  1497. return new string(text.Where(c => !separators.Contains(c)).ToArray());
  1498. }
  1499. /// <summary>
  1500. /// 验证搜索字段列表
  1501. /// </summary>
  1502. public static (bool IsValid, string Message, List<string> ValidFields) ValidateSearchFields(
  1503. List<string> searchFields, List<string> allFields)
  1504. {
  1505. if (searchFields == null || !searchFields.Any())
  1506. return (false, "搜索字段列表不能为空", new List<string>());
  1507. if (allFields == null || !allFields.Any())
  1508. return (true, "无验证字段列表,接受所有输入", searchFields);
  1509. var validFields = searchFields.Intersect(allFields).ToList();
  1510. var invalidFields = searchFields.Except(allFields).ToList();
  1511. if (invalidFields.Any())
  1512. {
  1513. return (false,
  1514. $"发现无效字段: {string.Join(", ", invalidFields)}",
  1515. validFields);
  1516. }
  1517. return (true, "所有字段有效", validFields);
  1518. }
  1519. /// <summary>
  1520. /// 生成搜索统计信息
  1521. /// </summary>
  1522. public static SearchStats GenerateSearchStats(SearchStructure structure, int resultCount)
  1523. {
  1524. return new SearchStats
  1525. {
  1526. SearchTerm = structure.OriginalTerm,
  1527. CombinationType = structure.CombinationType,
  1528. WordCount = structure.Words.Count,
  1529. CharCount = structure.SingleChars.Count,
  1530. TotalSegments = structure.Segments.Count,
  1531. ComplexityScore = structure.ComplexityScore,
  1532. ResultCount = resultCount,
  1533. GeneratedAt = DateTime.Now
  1534. };
  1535. }
  1536. #endregion
  1537. }
  1538. #region 数据模型和枚举
  1539. /// <summary>
  1540. /// 搜索模式枚举
  1541. /// </summary>
  1542. public enum SearchMode
  1543. {
  1544. /// <summary>
  1545. /// 自动检测搜索类型
  1546. /// </summary>
  1547. [Description("自动检测")]
  1548. AutoDetect = 0,
  1549. /// <summary>
  1550. /// 单词条搜索
  1551. /// </summary>
  1552. [Description("单词条搜索")]
  1553. SingleWord = 1,
  1554. /// <summary>
  1555. /// 多词条搜索
  1556. /// </summary>
  1557. [Description("多词条搜索")]
  1558. MultiWord = 2,
  1559. /// <summary>
  1560. /// 单字搜索
  1561. /// </summary>
  1562. [Description("单字搜索")]
  1563. SingleChar = 3,
  1564. /// <summary>
  1565. /// 精确匹配
  1566. /// </summary>
  1567. [Description("精确匹配")]
  1568. Exact = 4,
  1569. /// <summary>
  1570. /// 高级查询
  1571. /// </summary>
  1572. [Description("高级查询")]
  1573. Advanced = 5
  1574. }
  1575. /// <summary>
  1576. /// 组合模式枚举
  1577. /// </summary>
  1578. public enum CombinationMode
  1579. {
  1580. /// <summary>
  1581. /// 智能混合(自动调整权重和连接方式)
  1582. /// </summary>
  1583. [Description("智能混合")]
  1584. SmartMix = 0,
  1585. /// <summary>
  1586. /// 词语和单字组合(词语必须,单字增强)
  1587. /// </summary>
  1588. [Description("词语+单字")]
  1589. WordsAndChars = 1,
  1590. /// <summary>
  1591. /// 仅词语
  1592. /// </summary>
  1593. [Description("仅词语")]
  1594. WordsOnly = 2,
  1595. /// <summary>
  1596. /// 仅单字
  1597. /// </summary>
  1598. [Description("仅单字")]
  1599. CharsOnly = 3
  1600. }
  1601. /// <summary>
  1602. /// 组合类型
  1603. /// </summary>
  1604. public enum CombinationType
  1605. {
  1606. [Description("未知")]
  1607. Unknown,
  1608. [Description("仅词语")]
  1609. WordsOnly,
  1610. [Description("仅单字")]
  1611. CharsOnly,
  1612. [Description("词语和单字混合")]
  1613. WordsAndChars
  1614. }
  1615. /// <summary>
  1616. /// 搜索结构分析结果
  1617. /// </summary>
  1618. public class SearchStructure
  1619. {
  1620. public string OriginalTerm { get; set; } = "";
  1621. public string CleanTerm { get; set; } = "";
  1622. public DateTime AnalyzedAt { get; set; }
  1623. public List<string> Segments { get; set; } = new List<string>();
  1624. public List<string> Words { get; set; } = new List<string>();
  1625. public List<string> SingleChars { get; set; } = new List<string>();
  1626. public List<char> AllCharacters { get; set; } = new List<char>();
  1627. public bool HasWords { get; set; }
  1628. public bool HasSingleChars { get; set; }
  1629. public CombinationType CombinationType { get; set; }
  1630. public string Notes { get; set; } = "";
  1631. public int ComplexityScore { get; set; }
  1632. // 统计信息
  1633. public int TotalWordLength => Words.Sum(w => w.Length);
  1634. public int MaxWordLength => Words.Any() ? Words.Max(w => w.Length) : 0;
  1635. public double AvgWordLength => Words.Any() ? Words.Average(w => w.Length) : 0;
  1636. public int TotalCharacterCount => AllCharacters.Count;
  1637. }
  1638. /// <summary>
  1639. /// 搜索配置类(增强版)
  1640. /// 控制搜索行为、匹配策略和性能优化的参数集合
  1641. /// </summary>
  1642. public class SearchConfig
  1643. {
  1644. /// <summary>
  1645. /// 是否启用精确匹配
  1646. /// 默认值: true
  1647. /// 说明: 当设置为true时,系统会尝试使用等号(=)进行完全匹配
  1648. /// 适用场景: 搜索ID、编码、精确名称等唯一标识
  1649. /// 示例: 搜索"1001"时,只匹配完全等于"1001"的记录
  1650. /// 性能影响: 精确匹配通常使用索引,性能最好
  1651. /// </summary>
  1652. public bool EnableExactMatch { get; set; } = true;
  1653. /// <summary>
  1654. /// 是否启用前缀匹配
  1655. /// 默认值: true
  1656. /// 说明: 当设置为true时,系统会尝试匹配以搜索词开头的记录
  1657. /// 适用场景: 自动补全、拼音首字母搜索、编码前缀搜索
  1658. /// 示例: 搜索"北京"时,会匹配"北京市"、"北京旅游"等
  1659. /// 性能影响: 前缀匹配可以使用索引,性能良好
  1660. /// </summary>
  1661. public bool EnablePrefixMatch { get; set; } = true;
  1662. /// <summary>
  1663. /// 是否启用模糊搜索
  1664. /// 默认值: true
  1665. /// 说明: 当设置为true时,系统会使用LIKE '%关键词%'进行模糊匹配
  1666. /// 适用场景: 通用全文搜索、包含关系搜索
  1667. /// 示例: 搜索"长城"时,会匹配"长城旅游"、"八达岭长城"等
  1668. /// 性能影响: 模糊搜索不能使用普通索引,建议配合全文索引使用
  1669. /// 注意: 大数据量时可能会影响性能,建议合理设置搜索字段
  1670. /// </summary>
  1671. public bool EnableFuzzySearch { get; set; } = true;
  1672. /// <summary>
  1673. /// 是否启用单字搜索
  1674. /// 默认值: true
  1675. /// 说明: 当设置为true时,系统会将搜索词拆分成单个字符进行匹配
  1676. /// 适用场景: 中文单字搜索、记忆不完整的模糊搜索
  1677. /// 示例: 搜索"北上广"时,会分别匹配包含"北"、"上"、"广"的记录
  1678. /// 注意: 启用后会生成多个LIKE条件,可能影响性能
  1679. /// 建议: 单字数量较多时,考虑使用RequireAllChars控制匹配逻辑
  1680. /// </summary>
  1681. public bool EnableCharSearch { get; set; } = true;
  1682. /// <summary>
  1683. /// 是否要求所有词语都必须匹配
  1684. /// 默认值: true
  1685. /// 说明: 当设置为true时,多词搜索中所有词语都必须在记录中出现
  1686. /// 当设置为false时,任意一个词语出现即可匹配
  1687. /// 适用场景:
  1688. /// true - 精确搜索,如"北京 长城"要求同时包含"北京"和"长城"
  1689. /// false - 宽松搜索,如"北京 长城"匹配包含"北京"或"长城"的记录
  1690. /// 性能影响: true时可能返回更少结果,但条件更复杂
  1691. /// 用户体验: true时结果更精确,false时召回率更高
  1692. /// </summary>
  1693. public bool RequireAllWords { get; set; } = true;
  1694. /// <summary>
  1695. /// 是否要求所有单字都必须匹配
  1696. /// 默认值: true
  1697. /// 说明: 当设置为true时,单字搜索中所有字符都必须在记录中出现
  1698. /// 当设置为false时,任意一个字符出现即可匹配
  1699. /// 适用场景:
  1700. /// true - 如"北上广"要求同时包含"北"、"上"、"广"
  1701. /// false - 如"北上广"匹配包含"北"或"上"或"广"的记录
  1702. /// 注意: 单字数量较多时,建议设为false以提高召回率
  1703. /// </summary>
  1704. public bool RequireAllChars { get; set; } = true;
  1705. /// <summary>
  1706. /// 精确匹配的最小长度要求
  1707. /// 默认值: 2
  1708. /// 说明: 只有当搜索词长度大于等于此值时,才会尝试精确匹配
  1709. /// 适用场景: 避免短词(如"a"、"的")使用精确匹配
  1710. /// 示例: 设置为3时,"北京"不会使用精确匹配,"北京市"会使用
  1711. /// 单位: 字符数
  1712. /// 建议值: 2-5之间,根据业务需求调整
  1713. /// </summary>
  1714. public int MinExactMatchLength { get; set; } = 2;
  1715. /// <summary>
  1716. /// 搜索词的最大长度限制
  1717. /// 默认值: 200
  1718. /// 说明: 超过此长度的搜索词会被自动截断
  1719. /// 目的:
  1720. /// 1. 防止恶意输入导致性能问题
  1721. /// 2. 避免过长的SQL语句
  1722. /// 3. 提高系统安全性
  1723. /// 注意: 实际截断时保留前面部分,不会影响搜索意图
  1724. /// 单位: 字符数
  1725. /// 建议值: 50-500之间,根据业务场景调整
  1726. /// </summary>
  1727. public int MaxSearchTermLength { get; set; } = 200;
  1728. /// <summary>
  1729. /// 匹配度的最低分数要求
  1730. /// 默认值: 0
  1731. /// 说明: 只有匹配度分数大于等于此值的记录才会被返回
  1732. /// 适用场景: 质量控制,过滤掉相关性太低的结果
  1733. /// 分数范围: 通常为0-100,具体取决于评分算法
  1734. /// 示例: 设置为10时,匹配度低于10分的记录会被过滤
  1735. /// 注意: 需要EnableRelevanceScoring=true才会生效
  1736. /// </summary>
  1737. public double MinScore { get; set; } = 0;
  1738. /// <summary>
  1739. /// 是否启用相关性评分
  1740. /// 默认值: true
  1741. /// 说明: 当设置为true时,系统会计算每条记录的匹配度分数
  1742. /// 功能:
  1743. /// 1. 按匹配度排序(分数高的在前)
  1744. /// 2. 可以设置MinScore过滤低分记录
  1745. /// 3. 提供MatchScore字段供前端显示
  1746. /// 性能影响: 会增加计算开销,但用户体验更好
  1747. /// 建议: 搜索结果需要排序时启用,简单列表查询可关闭
  1748. /// </summary>
  1749. public bool EnableRelevanceScoring { get; set; } = true;
  1750. /// <summary>
  1751. /// 是否启用分阶段搜索
  1752. /// 默认值: false
  1753. /// 说明: 当设置为true时,系统会按优先级分阶段执行搜索
  1754. /// 搜索阶段:
  1755. /// 1. 精确匹配(最高优先级)
  1756. /// 2. 前缀匹配
  1757. /// 3. 模糊匹配
  1758. /// 4. 单字匹配(最低优先级)
  1759. /// 优点:
  1760. /// 1. 优先返回最相关的结果
  1761. /// 2. 可以设置降级策略
  1762. /// 3. 提高搜索成功率
  1763. /// 缺点:
  1764. /// 1. 可能执行多次查询
  1765. /// 2. 逻辑更复杂
  1766. /// 适用场景: 对搜索结果质量要求较高的场景
  1767. /// </summary>
  1768. public bool EnableStagedSearch { get; set; } = false;
  1769. /// <summary>
  1770. /// 字段权重配置
  1771. /// 默认值: 预定义的权重字典
  1772. /// 说明: 控制不同字段在匹配度计算中的重要性
  1773. /// 权重范围: 建议0.5-2.0之间
  1774. /// 示例配置:
  1775. /// "name": 1.5 // 名称字段权重高
  1776. /// "title": 1.5 // 标题字段权重高
  1777. /// "code": 1.3 // 编码字段权重中
  1778. /// "desc": 1.0 // 描述字段权重正常
  1779. /// "remark": 0.7 // 备注字段权重低
  1780. /// 使用方式:
  1781. /// 1. 匹配度分数 = 基础分 × 字段权重
  1782. /// 2. 权重高的字段对总分影响更大
  1783. /// 建议: 根据业务重要性调整权重
  1784. /// </summary>
  1785. public Dictionary<string, double> FieldWeights { get; set; } = new Dictionary<string, double>
  1786. {
  1787. { "name", 1.5 },
  1788. { "title", 1.5 },
  1789. { "code", 1.3 },
  1790. { "description", 1.0 },
  1791. { "content", 0.8 },
  1792. { "remark", 0.7 }
  1793. };
  1794. /// <summary>
  1795. /// 是否启用同义词扩展
  1796. /// 默认值: false
  1797. /// 说明: 当设置为true时,系统会自动扩展搜索词的同义词
  1798. /// 示例: 搜索"电脑"时,也会搜索"计算机"、"微机"等
  1799. /// 实现方式: 需要配合同义词词典使用
  1800. /// 优点: 提高召回率,改善搜索体验
  1801. /// 缺点: 可能引入不相关结果
  1802. /// 适用场景: 专业术语搜索、方言词汇搜索
  1803. /// </summary>
  1804. public bool EnableSynonymExpansion { get; set; } = false;
  1805. /// <summary>
  1806. /// 是否启用拼音搜索
  1807. /// 默认值: false
  1808. /// 说明: 当设置为true时,系统会将中文转换为拼音进行搜索
  1809. /// 示例: 搜索"beijing"可以匹配"北京"
  1810. /// 实现方式: 需要拼音转换库
  1811. /// 适用场景:
  1812. /// 1. 输入拼音搜索中文
  1813. /// 2. 首字母搜索(如"bj"匹配"北京")
  1814. /// 注意: 会增加搜索复杂度和索引大小
  1815. /// </summary>
  1816. public bool EnablePinyinSearch { get; set; } = false;
  1817. /// <summary>
  1818. /// 搜索结果的最大数量限制
  1819. /// 默认值: 1000
  1820. /// 说明: 限制单次搜索返回的最大记录数
  1821. /// 目的:
  1822. /// 1. 防止返回过多数据影响性能
  1823. /// 2. 避免内存溢出
  1824. /// 3. 提高响应速度
  1825. /// 注意: 分页查询时,此限制针对总记录数
  1826. /// 建议值: 500-5000之间,根据系统承载能力调整
  1827. /// </summary>
  1828. public int MaxResultCount { get; set; } = 1000;
  1829. /// <summary>
  1830. /// 是否启用搜索缓存
  1831. /// 默认值: false
  1832. /// 说明: 当设置为true时,相同的搜索条件会使用缓存结果
  1833. /// 缓存策略:
  1834. /// 1. 基于搜索词和参数的哈希值
  1835. /// 2. 可设置缓存时间
  1836. /// 3. 支持滑动过期
  1837. /// 适用场景:
  1838. /// 1. 热门搜索词
  1839. /// 2. 数据更新频率低的场景
  1840. /// 3. 性能要求高的搜索
  1841. /// 注意: 数据频繁更新时,需要合理设置缓存时间
  1842. /// </summary>
  1843. public bool EnableSearchCache { get; set; } = false;
  1844. /// <summary>
  1845. /// 缓存时间(分钟)
  1846. /// 默认值: 5
  1847. /// 说明: 搜索结果的缓存保留时间
  1848. /// 单位: 分钟
  1849. /// 使用条件: 需要EnableSearchCache=true
  1850. /// 建议值:
  1851. /// - 高频搜索: 1-5分钟
  1852. /// - 低频搜索: 10-30分钟
  1853. /// - 静态数据: 60分钟以上
  1854. /// </summary>
  1855. public int CacheDurationMinutes { get; set; } = 5;
  1856. /// <summary>
  1857. /// 是否启用搜索词分析
  1858. /// 默认值: true
  1859. /// 说明: 当设置为true时,系统会对搜索词进行智能分析
  1860. /// 分析内容:
  1861. /// 1. 去除停用词(的、了、在等)
  1862. /// 2. 提取关键词
  1863. /// 3. 识别搜索意图
  1864. /// 4. 分析词性
  1865. /// 优点: 提高搜索准确性和智能性
  1866. /// 性能影响: 轻微,主要消耗在分析算法上
  1867. /// 建议: 一般保持启用状态
  1868. /// </summary>
  1869. public bool EnableQueryAnalysis { get; set; } = true;
  1870. /// <summary>
  1871. /// 是否记录搜索日志
  1872. /// 默认值: false
  1873. /// 说明: 当设置为true时,系统会记录搜索行为日志
  1874. /// 记录内容:
  1875. /// 1. 搜索词
  1876. /// 2. 搜索时间
  1877. /// 3. 返回结果数
  1878. /// 4. 响应时间
  1879. /// 5. 用户信息(可选)
  1880. /// 用途:
  1881. /// 1. 分析用户搜索习惯
  1882. /// 2. 优化搜索算法
  1883. /// 3. 监控搜索性能
  1884. /// 4. 安全审计
  1885. /// 注意: 涉及隐私时需谨慎处理
  1886. /// </summary>
  1887. public bool EnableSearchLogging { get; set; } = false;
  1888. /// <summary>
  1889. /// 搜索超时时间(秒)
  1890. /// 默认值: 30
  1891. /// 说明: 搜索操作的最大执行时间
  1892. /// 目的:
  1893. /// 1. 防止长时间运行的查询
  1894. /// 2. 提高系统稳定性
  1895. /// 3. 改善用户体验
  1896. /// 单位: 秒
  1897. /// 建议值:
  1898. /// - 实时搜索: 5-10秒
  1899. /// - 后台搜索: 30-60秒
  1900. /// - 大数据搜索: 120秒以上
  1901. /// 注意: 超时后会取消查询,返回已有结果或错误
  1902. /// </summary>
  1903. public int SearchTimeoutSeconds { get; set; } = 30;
  1904. /// <summary>
  1905. /// 是否启用异步搜索
  1906. /// 默认值: true
  1907. /// 说明: 当设置为true时,搜索操作会异步执行
  1908. /// 优点:
  1909. /// 1. 不阻塞主线程
  1910. /// 2. 提高并发处理能力
  1911. /// 3. 更好的用户体验
  1912. /// 适用场景:
  1913. /// 1. 复杂搜索条件
  1914. /// 2. 大数据量搜索
  1915. /// 3. 高并发场景
  1916. /// 注意: 需要合理的线程管理和资源控制
  1917. /// </summary>
  1918. public bool EnableAsyncSearch { get; set; } = true;
  1919. /// <summary>
  1920. /// 是否启用智能提示
  1921. /// 默认值: false
  1922. /// 说明: 当设置为true时,系统会根据输入提供搜索建议
  1923. /// 功能:
  1924. /// 1. 自动补全
  1925. /// 2. 热门搜索建议
  1926. /// 3. 相关搜索推荐
  1927. /// 4. 拼写纠正
  1928. /// 实现方式: 基于搜索历史、词典、算法模型
  1929. /// 适用场景: 搜索框智能提示功能
  1930. /// 注意: 需要额外的数据处理和存储
  1931. /// </summary>
  1932. public bool EnableSmartSuggestions { get; set; } = false;
  1933. /// <summary>
  1934. /// 是否启用搜索结果高亮
  1935. /// 默认值: false
  1936. /// 说明: 当设置为true时,搜索结果中的匹配部分会被高亮显示
  1937. /// 高亮方式:
  1938. /// 1. HTML标记(如<em>关键词</em>)
  1939. /// 2. 特殊字符包裹
  1940. /// 3. 前端处理
  1941. /// 适用场景: 需要突出显示匹配内容的搜索
  1942. /// 注意: 会增加数据传输量和前端处理复杂度
  1943. /// </summary>
  1944. public bool EnableResultHighlighting { get; set; } = false;
  1945. /// <summary>
  1946. /// 是否启用搜索结果分组
  1947. /// 默认值: false
  1948. /// 说明: 当设置为true时,搜索结果会按指定字段进行分组
  1949. /// 分组方式:
  1950. /// 1. 按类型分组
  1951. /// 2. 按时间分组
  1952. /// 3. 按相关度分组
  1953. /// 适用场景:
  1954. /// 1. 多类型混合搜索
  1955. /// 2. 时间线展示
  1956. /// 3. 分类浏览
  1957. /// 注意: 会增加查询复杂度和处理时间
  1958. /// </summary>
  1959. public bool EnableResultGrouping { get; set; } = false;
  1960. /// <summary>
  1961. /// 是否启用智能拆分
  1962. /// 默认值: true
  1963. /// 说明: 当设置为true时,系统会自动将长词拆分成合理的子词组合
  1964. /// 适用场景: 搜索"重庆科学"这类组合词时,会自动尝试"重庆"+"科学"的组合
  1965. /// 工作方式:
  1966. /// 1. 检测长度为MinSplitLength及以上的词语
  1967. /// 2. 使用智能算法判断是否为可拆分组合词
  1968. /// 3. 如果可拆分,生成多层次搜索条件
  1969. /// 多层次条件示例: "重庆科学" →
  1970. /// - 完整词匹配(最高优先级)
  1971. /// - 拆分子词匹配("重庆" AND "科学")
  1972. /// - 单字匹配("重" OR "庆" OR "科" OR "学")
  1973. /// 优点:
  1974. /// 1. 提高组合词的搜索成功率
  1975. /// 2. 保持搜索结果的准确性
  1976. /// 3. 提升用户体验
  1977. /// 性能影响: 会增加搜索条件复杂度,但能显著提高召回率
  1978. /// 建议: 中文搜索场景建议启用
  1979. /// </summary>
  1980. public bool EnableSmartSplitting { get; set; } = true;
  1981. /// <summary>
  1982. /// 智能拆分时是否要求所有子词都匹配
  1983. /// 默认值: false
  1984. /// 说明: 控制智能拆分时对拆分子词的匹配严格度
  1985. /// 示例:
  1986. /// - true: "重庆科学"拆分为"重庆"和"科学",要求同时包含这两个词
  1987. /// - false: "重庆科学"拆分为"重庆"和"科学",包含任意一个即可匹配
  1988. /// 适用场景:
  1989. /// true: 需要精确拆分匹配的场景
  1990. /// false: 需要提高召回率的场景
  1991. /// 建议: 通常设置为false以获得更好的搜索结果覆盖率
  1992. /// 注意: 此设置仅影响智能拆分的子词匹配,不影响原始词语匹配
  1993. /// </summary>
  1994. public bool RequireAllWordsForSplit { get; set; } = false;
  1995. /// <summary>
  1996. /// 智能拆分时是否要求所有单字都匹配
  1997. /// 默认值: false
  1998. /// 说明: 控制智能拆分成单字时的匹配严格度
  1999. /// 示例:
  2000. /// - true: "重庆科学"拆分为单字时,要求同时包含"重"、"庆"、"科"、"学"
  2001. /// - false: "重庆科学"拆分为单字时,包含任意一个单字即可匹配
  2002. /// 适用场景:
  2003. /// true: 需要完全匹配所有单字的严格场景
  2004. /// false: 需要宽松匹配以提高召回率的场景
  2005. /// 建议: 通常设置为false,因为单字完全匹配要求较高,容易漏掉相关结果
  2006. /// 注意: 此设置仅影响智能拆分的单字匹配阶段
  2007. /// </summary>
  2008. public bool RequireAllCharsForSplit { get; set; } = false;
  2009. /// <summary>
  2010. /// 最小拆分长度
  2011. /// 默认值: 4
  2012. /// 说明: 只有长度大于等于此值的词才会被考虑进行智能拆分
  2013. /// 单位: 字符数
  2014. /// 示例:
  2015. /// - 设置为4时,"重庆科学"(4字)会被考虑拆分
  2016. /// - 设置为5时,"北京长城游"(5字)会被考虑拆分
  2017. /// 适用场景:
  2018. /// - 较低值(3-4):适用于短词较多的场景
  2019. /// - 较高值(5-6):适用于长词较多的专业场景
  2020. /// 建议值:
  2021. /// - 中文搜索: 4
  2022. /// - 英文搜索: 8(对应4个英文单词)
  2023. /// - 混合搜索: 根据主要语言调整
  2024. /// 注意: 设置过低可能导致过多词语被拆分,影响性能;设置过高可能错过可拆分的组合词
  2025. /// </summary>
  2026. public int MinSplitLength { get; set; } = 4;
  2027. /// <summary>
  2028. /// 最大拆分组合数
  2029. /// 默认值: 3
  2030. /// 说明: 限制一个词的最大拆分方式数量,防止组合爆炸
  2031. /// 示例:
  2032. /// - 设置为3时,一个6字词最多尝试3种拆分方式
  2033. /// - 设置为1时,只使用最优的拆分方式
  2034. /// 适用场景:
  2035. /// - 性能敏感场景: 1-2
  2036. /// - 平衡场景: 3
  2037. /// - 准确性优先场景: 5-10
  2038. /// 性能影响: 每增加一个拆分组合,会增加一个搜索条件分支
  2039. /// 建议:
  2040. /// - 实时搜索: 1-3
  2041. /// - 后台搜索: 3-5
  2042. /// - 复杂分析: 5-10
  2043. /// 注意: 过高的值可能导致SQL条件过于复杂,影响数据库性能
  2044. /// </summary>
  2045. public int MaxSplitCombinations { get; set; } = 3;
  2046. }
  2047. /// <summary>
  2048. /// 搜索阶段
  2049. /// </summary>
  2050. public class SearchStage
  2051. {
  2052. public int Stage { get; set; }
  2053. public string Name { get; set; } = "";
  2054. public string Condition { get; set; } = "";
  2055. public List<SugarParameter> Parameters { get; set; } = new List<SugarParameter>();
  2056. public int Priority { get; set; }
  2057. public string Description { get; set; } = "";
  2058. public bool FallbackToNext { get; set; } = true;
  2059. }
  2060. /// <summary>
  2061. /// 相关性信息(增强版)
  2062. /// </summary>
  2063. public class RelevanceInfo
  2064. {
  2065. public SearchStructure SearchStructure { get; set; }
  2066. public int ExpectedMatches { get; set; }
  2067. public List<string> BoostFields { get; set; } = new List<string>();
  2068. public List<ScoringRule> ScoringRules { get; set; } = new List<ScoringRule>();
  2069. public MatchStrategy MatchStrategy { get; set; }
  2070. public DateTime GeneratedAt { get; set; }
  2071. public bool HasSmartSplitting { get; set; }
  2072. public List<string> SplittableWords { get; set; } = new List<string>();
  2073. }
  2074. /// <summary>
  2075. /// 匹配策略(增强版)
  2076. /// </summary>
  2077. public class MatchStrategy
  2078. {
  2079. public string StrategyName { get; set; } = "";
  2080. public int ComplexityScore { get; set; }
  2081. public bool UseExactMatch { get; set; }
  2082. public bool UsePrefixMatch { get; set; }
  2083. public bool UseFuzzySearch { get; set; }
  2084. public bool UseCharSearch { get; set; }
  2085. public bool UseSmartSplitting { get; set; }
  2086. public int RecommendedPageSize { get; set; } = 20;
  2087. public string Description { get; set; } = "";
  2088. }
  2089. /// <summary>
  2090. /// 计分规则
  2091. /// </summary>
  2092. public class ScoringRule
  2093. {
  2094. public string Type { get; set; } = ""; // word, char, combo, exact, smart_split
  2095. public string Value { get; set; } = "";
  2096. public int BaseScore { get; set; }
  2097. public int Bonus { get; set; }
  2098. public double Multiplier { get; set; } = 1.0;
  2099. public string Condition { get; set; } = "";
  2100. }
  2101. /// <summary>
  2102. /// 搜索统计信息
  2103. /// </summary>
  2104. public class SearchStats
  2105. {
  2106. public string SearchTerm { get; set; } = "";
  2107. public CombinationType CombinationType { get; set; }
  2108. public int WordCount { get; set; }
  2109. public int CharCount { get; set; }
  2110. public int TotalSegments { get; set; }
  2111. public int ComplexityScore { get; set; }
  2112. public int ResultCount { get; set; }
  2113. public DateTime GeneratedAt { get; set; }
  2114. public double MatchRatio => TotalSegments > 0 ? (double)ResultCount / TotalSegments : 0;
  2115. public string ComplexityLevel => ComplexityScore >= 7 ? "高" :
  2116. ComplexityScore >= 4 ? "中" : "低";
  2117. }
  2118. #endregion
  2119. #region 扩展方法
  2120. /// <summary>
  2121. /// AdvancedSearchHelper 扩展方法
  2122. /// </summary>
  2123. public static class EnhancedSearchExtensions
  2124. {
  2125. /// <summary>
  2126. /// 获取组合类型的详细说明
  2127. /// </summary>
  2128. public static string GetDetail(this CombinationType type)
  2129. {
  2130. return type switch
  2131. {
  2132. CombinationType.WordsOnly => "仅使用词语进行搜索,适合精确的关键词匹配",
  2133. CombinationType.CharsOnly => "仅使用单字进行搜索,适合模糊或记忆不完整的搜索",
  2134. CombinationType.WordsAndChars => "混合使用词语和单字,提供更灵活的搜索体验",
  2135. _ => "未知的搜索类型"
  2136. };
  2137. }
  2138. /// <summary>
  2139. /// 获取搜索模式的推荐使用场景
  2140. /// </summary>
  2141. public static string GetUsageScenario(this SearchMode mode)
  2142. {
  2143. return mode switch
  2144. {
  2145. SearchMode.AutoDetect => "适用于通用搜索场景,系统自动识别最佳搜索方式",
  2146. SearchMode.SingleWord => "适用于已知确切名称或关键词的搜索",
  2147. SearchMode.MultiWord => "适用于多个相关关键词的组合搜索",
  2148. SearchMode.SingleChar => "适用于模糊搜索或拼音首字母搜索",
  2149. SearchMode.Exact => "适用于需要完全匹配的精确搜索",
  2150. SearchMode.Advanced => "适用于专业用户的高级搜索需求",
  2151. _ => "通用搜索场景"
  2152. };
  2153. }
  2154. }
  2155. #endregion
  2156. }