PostService.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using Masuit.LuceneEFCore.SearchEngine;
  2. using Masuit.LuceneEFCore.SearchEngine.Interfaces;
  3. using Masuit.MyBlogs.Core.Common;
  4. using Masuit.MyBlogs.Core.Infrastructure.Repository.Interface;
  5. using Masuit.MyBlogs.Core.Infrastructure.Services.Interface;
  6. using Masuit.MyBlogs.Core.Models.DTO;
  7. using Masuit.MyBlogs.Core.Models.Entity;
  8. using Masuit.MyBlogs.Core.Models.Enum;
  9. using Microsoft.EntityFrameworkCore.Internal;
  10. using Microsoft.Extensions.Caching.Memory;
  11. using PanGu;
  12. using PanGu.HighLight;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.Linq;
  16. using System.Linq.Expressions;
  17. using System.Threading.Tasks;
  18. namespace Masuit.MyBlogs.Core.Infrastructure.Services
  19. {
  20. public partial class PostService : BaseService<Post>, IPostService
  21. {
  22. private readonly IMemoryCache _memoryCache;
  23. public PostService(IPostRepository repository, ISearchEngine<DataContext> searchEngine, ILuceneIndexSearcher searcher, IMemoryCache memoryCache) : base(repository, searchEngine, searcher)
  24. {
  25. _memoryCache = memoryCache;
  26. }
  27. public SearchResult<PostOutputDto> SearchPage(int page, int size, string keyword)
  28. {
  29. var cacheKey = $"search:{keyword}:{page}:{size}";
  30. if (_memoryCache.TryGetValue<SearchResult<PostOutputDto>>(cacheKey, out var value))
  31. {
  32. return value;
  33. }
  34. var searchResult = _searchEngine.ScoredSearch<Post>(BuildSearchOptions(page, size, keyword));
  35. var posts = searchResult.Results.Select(p => p.Entity.Mapper<PostOutputDto>()).Where(p => p.Status == Status.Pended).ToList();
  36. var simpleHtmlFormatter = new SimpleHTMLFormatter("<span style='color:red;background-color:yellow;font-size: 1.1em;font-weight:700;'>", "</span>");
  37. var highlighter = new Highlighter(simpleHtmlFormatter, new Segment()) { FragmentSize = 200 };
  38. var keywords = _searcher.CutKeywords(keyword);
  39. foreach (var p in posts)
  40. {
  41. foreach (var s in keywords)
  42. {
  43. string frag;
  44. if (p.Title.Contains(s) && !string.IsNullOrEmpty(frag = highlighter.GetBestFragment(s, p.Title)))
  45. {
  46. p.Title = frag;
  47. break;
  48. }
  49. }
  50. bool handled = false;
  51. foreach (var s in keywords)
  52. {
  53. string frag;
  54. if (p.Content.Contains(s) && !string.IsNullOrEmpty(frag = highlighter.GetBestFragment(s, p.Content)))
  55. {
  56. p.Content = frag;
  57. handled = true;
  58. break;
  59. }
  60. }
  61. if (p.Content.Length > 200 && !handled)
  62. {
  63. p.Content = p.Content.Substring(0, 200);
  64. }
  65. }
  66. var result = new SearchResult<PostOutputDto>()
  67. {
  68. Results = posts,
  69. Elapsed = searchResult.Elapsed,
  70. Total = searchResult.TotalHits
  71. };
  72. return _memoryCache.Set(cacheKey, result, TimeSpan.FromHours(1));
  73. }
  74. private static SearchOptions BuildSearchOptions(int page, int size, string keyword)
  75. {
  76. var fields = new List<string>();
  77. var newkeywords = new List<string>();
  78. if (keyword.Contains("intitle:"))
  79. {
  80. fields.Add("Title");
  81. newkeywords.Add(keyword.Split(' ', ' ').FirstOrDefault(s => s.Contains("intitle")).Split(':')[1]);
  82. }
  83. if (keyword.Contains("content:"))
  84. {
  85. fields.Add("Content");
  86. newkeywords.Add(keyword.Split(' ', ' ').FirstOrDefault(s => s.Contains("content")).Split(':')[1]);
  87. }
  88. var searchOptions = fields.Any() ? new SearchOptions(newkeywords.Join(" "), page, size, fields.Join(",")) : new SearchOptions(keyword, page, size, typeof(Post));
  89. return searchOptions;
  90. }
  91. /// <summary>
  92. /// 添加实体并保存
  93. /// </summary>
  94. /// <param name="t">需要添加的实体</param>
  95. /// <returns>添加成功</returns>
  96. public override Post AddEntitySaved(Post t)
  97. {
  98. t = base.AddEntity(t);
  99. _searchEngine.SaveChanges(t.Status == Status.Pended);
  100. return t;
  101. }
  102. /// <summary>
  103. /// 添加实体并保存(异步)
  104. /// </summary>
  105. /// <param name="t">需要添加的实体</param>
  106. /// <returns>添加成功</returns>
  107. public override Task<int> AddEntitySavedAsync(Post t)
  108. {
  109. base.AddEntity(t);
  110. return _searchEngine.SaveChangesAsync(t.Status == Status.Pended);
  111. }
  112. /// <summary>
  113. /// 根据ID删除实体并保存
  114. /// </summary>
  115. /// <param name="id">实体id</param>
  116. /// <returns>删除成功</returns>
  117. public override bool DeleteByIdSaved(object id)
  118. {
  119. base.DeleteById(id);
  120. return _searchEngine.SaveChanges() > 0;
  121. }
  122. /// <summary>
  123. /// 删除多个实体并保存
  124. /// </summary>
  125. /// <param name="list">实体集合</param>
  126. /// <returns>删除成功</returns>
  127. public override bool DeleteEntitiesSaved(IEnumerable<Post> list)
  128. {
  129. base.DeleteEntities(list);
  130. return _searchEngine.SaveChanges() > 0;
  131. }
  132. /// <summary>
  133. /// 根据ID删除实体并保存(异步)
  134. /// </summary>
  135. /// <param name="id">实体id</param>
  136. /// <returns>删除成功</returns>
  137. public override Task<int> DeleteByIdSavedAsync(object id)
  138. {
  139. base.DeleteById(id);
  140. return _searchEngine.SaveChangesAsync();
  141. }
  142. /// <summary>
  143. /// 删除多个实体并保存(异步)
  144. /// </summary>
  145. /// <param name="list">实体集合</param>
  146. /// <returns>删除成功</returns>
  147. public override Task<int> DeleteEntitiesSavedAsync(IEnumerable<Post> list)
  148. {
  149. base.DeleteEntities(list);
  150. return _searchEngine.SaveChangesAsync();
  151. }
  152. /// <summary>
  153. /// 根据条件删除实体
  154. /// </summary>
  155. /// <param name="where">查询条件</param>
  156. /// <returns>删除成功</returns>
  157. public override int DeleteEntitySaved(Expression<Func<Post, bool>> @where)
  158. {
  159. base.DeleteEntity(@where);
  160. return _searchEngine.SaveChanges();
  161. }
  162. /// <summary>
  163. /// 删除实体并保存
  164. /// </summary>
  165. /// <param name="t">需要删除的实体</param>
  166. /// <returns>删除成功</returns>
  167. public override bool DeleteEntitySaved(Post t)
  168. {
  169. base.DeleteEntity(t);
  170. return _searchEngine.SaveChanges() > 0;
  171. }
  172. /// <summary>
  173. /// 根据条件删除实体
  174. /// </summary>
  175. /// <param name="where">查询条件</param>
  176. /// <returns>删除成功</returns>
  177. public override Task<int> DeleteEntitySavedAsync(Expression<Func<Post, bool>> @where)
  178. {
  179. base.DeleteEntity(@where);
  180. return _searchEngine.SaveChangesAsync();
  181. }
  182. /// <summary>
  183. /// 删除实体并保存(异步)
  184. /// </summary>
  185. /// <param name="t">需要删除的实体</param>
  186. /// <returns>删除成功</returns>
  187. public override Task<int> DeleteEntitySavedAsync(Post t)
  188. {
  189. base.DeleteEntity(t);
  190. return _searchEngine.SaveChangesAsync();
  191. }
  192. }
  193. }