298 lines
12 KiB
C#
298 lines
12 KiB
C#
using FreeSql;
|
||
|
||
using LiveForum.Code.Base;
|
||
using LiveForum.Code.JwtInfrastructure;
|
||
using LiveForum.IService.Others;
|
||
using LiveForum.Model;
|
||
using LiveForum.Model.Dto.Others;
|
||
|
||
using Microsoft.AspNetCore.Hosting;
|
||
using Microsoft.Extensions.Configuration;
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace LiveForum.Service.Others
|
||
{
|
||
public class SearchService : ISearchService
|
||
{
|
||
private readonly IBaseRepository<T_Posts> _postsRepository;
|
||
private readonly IBaseRepository<T_Users> _usersRepository;
|
||
private readonly IBaseRepository<T_Follows> _followsRepository;
|
||
private readonly IBaseRepository<T_UserTokens> _tokensRepository;
|
||
private readonly JwtUserInfoModel _userInfoModel;
|
||
private readonly IConfiguration _configuration;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="postsRepository">帖子仓储</param>
|
||
/// <param name="usersRepository">用户仓储</param>
|
||
/// <param name="followsRepository">用户关注仓储</param>
|
||
/// <param name="tokensRepository">token仓储</param>
|
||
/// <param name="userInfoModel">用户信息模型</param>
|
||
/// <param name="configuration">配置</param>
|
||
public SearchService(
|
||
IBaseRepository<T_Posts> postsRepository,
|
||
IBaseRepository<T_Users> usersRepository,
|
||
IBaseRepository<T_Follows> followsRepository,
|
||
IBaseRepository<T_UserTokens> tokensRepository,
|
||
JwtUserInfoModel userInfoModel,
|
||
IConfiguration configuration)
|
||
{
|
||
_postsRepository = postsRepository;
|
||
_usersRepository = usersRepository;
|
||
_followsRepository = followsRepository;
|
||
_userInfoModel = userInfoModel;
|
||
_tokensRepository = tokensRepository;
|
||
_configuration = configuration;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 搜索
|
||
/// </summary>
|
||
/// <param name="request">请求参数</param>
|
||
/// <returns></returns>
|
||
public async Task<BaseResponse<SearchRespDto>> Search(SearchReq request)
|
||
{
|
||
try
|
||
{
|
||
// 验证搜索关键词
|
||
if (string.IsNullOrWhiteSpace(request.Keyword))
|
||
{
|
||
return new BaseResponse<SearchRespDto>(ResponseCode.Error, "搜索关键词不能为空");
|
||
}
|
||
|
||
// 验证搜索类型
|
||
if (request.SearchType != 1 && request.SearchType != 2)
|
||
{
|
||
return new BaseResponse<SearchRespDto>(ResponseCode.Error, "无效的搜索类型");
|
||
}
|
||
|
||
var keyword = request.Keyword.Trim();
|
||
|
||
// 记录搜索历史(可选)
|
||
// await RecordSearchHistory(keyword);
|
||
|
||
if (request.SearchType == 1)
|
||
{
|
||
// 搜索帖子
|
||
return await SearchPosts(keyword, request);
|
||
}
|
||
else
|
||
{
|
||
// 搜索用户
|
||
return await SearchUsers(keyword, request);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return new BaseResponse<SearchRespDto>(ResponseCode.Error, $"搜索失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 搜索帖子
|
||
/// </summary>
|
||
/// <param name="keyword">关键词</param>
|
||
/// <param name="request">请求参数</param>
|
||
/// <returns></returns>
|
||
private async Task<BaseResponse<SearchRespDto>> SearchPosts(string keyword, SearchReq request)
|
||
{
|
||
var query = _postsRepository.Select
|
||
.Where(x => !x.IsDeleted && x.Status == Model.Enum.Posts.PostsStatusEnum.Publish)
|
||
.Where(x => x.Title.Contains(keyword) || x.Content.Contains(keyword))
|
||
.OrderByDescending(x => x.PublishTime);
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 分页查询
|
||
var posts = await query
|
||
.Page(request.PageIndex, request.PageSize)
|
||
.ToListAsync();
|
||
|
||
// 获取当前用户ID(如果已登录)
|
||
var currentUserId = _userInfoModel.UserId;
|
||
|
||
// 获取发帖用户信息
|
||
var userIds = posts.Select(p => p.UserId).Distinct().ToList();
|
||
var users = await _usersRepository.Select
|
||
.Where(u => userIds.Contains(u.Id))
|
||
.ToListAsync();
|
||
|
||
var userDict = users.ToDictionary(u => u.Id);
|
||
|
||
// 构建返回结果
|
||
var items = new List<object>();
|
||
foreach (var post in posts)
|
||
{
|
||
var user = userDict.GetValueOrDefault(post.UserId);
|
||
items.Add(new SearchPostDto
|
||
{
|
||
PostId = post.Id,
|
||
Title = post.Title,
|
||
Content = post.Content.Length > 200 ? post.Content.Substring(0, 200) + "..." : post.Content,
|
||
CoverImage = post.CoverImage,
|
||
LikeCount = post.LikeCount,
|
||
CommentCount = post.CommentCount,
|
||
PublishTime = post.PublishTime ?? post.CreatedAt,
|
||
User = user != null ? new SearchUserDto
|
||
{
|
||
UserId = user.Id,
|
||
UserName = "", // T_Users 没有 UserName 字段
|
||
NickName = user.NickName ?? "",
|
||
Avatar = user.Avatar ?? "",
|
||
IsVip = user.IsVip,
|
||
IsCertified = user.IsCertified,
|
||
Signature = user.Signature ?? "",
|
||
FollowerCount = 0, // 需要从 T_UserFollows 统计
|
||
IsFollowed = currentUserId > 0 && await IsFollowingUser(currentUserId, user.Id)
|
||
} : null
|
||
});
|
||
}
|
||
|
||
var totalPages = (int)Math.Ceiling(total / (double)request.PageSize);
|
||
|
||
return new BaseResponse<SearchRespDto>(new SearchRespDto
|
||
{
|
||
PageIndex = request.PageIndex,
|
||
PageSize = request.PageSize,
|
||
Total =(int)total,
|
||
TotalPages = totalPages,
|
||
Items = items
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 搜索用户
|
||
/// </summary>
|
||
/// <param name="keyword">关键词</param>
|
||
/// <param name="request">请求参数</param>
|
||
/// <returns></returns>
|
||
private async Task<BaseResponse<SearchRespDto>> SearchUsers(string keyword, SearchReq request)
|
||
{
|
||
var query = _usersRepository.Select
|
||
.Where(x => x.Status == Model.Enum.Users.UserStatusEnum.Normal)
|
||
.Where(x => x.NickName.Contains(keyword) || x.Signature.Contains(keyword));
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 分页查询
|
||
var users = await query
|
||
.Page(request.PageIndex, request.PageSize)
|
||
.OrderByDescending(x => x.CreatedAt)
|
||
.ToListAsync();
|
||
|
||
// 获取当前用户ID(如果已登录)
|
||
var currentUserId = _userInfoModel.UserId;
|
||
|
||
// 获取关注信息
|
||
var userIds = users.Select(u => u.Id).ToList();
|
||
var followInfo = currentUserId > 0
|
||
? await _followsRepository.Select
|
||
.Where(f => f.FollowerId == currentUserId && userIds.Contains(f.FollowedUserId))
|
||
.ToListAsync()
|
||
: new List<T_Follows>();
|
||
|
||
var followSet = followInfo.Select(f => f.FollowedUserId).ToHashSet();
|
||
|
||
// 统计每个用户的粉丝数
|
||
var followerCountsDict = new Dictionary<long, int>();
|
||
foreach (var userId in userIds)
|
||
{
|
||
var count = await _followsRepository.Select
|
||
.Where(f => f.FollowedUserId == userId)
|
||
.CountAsync();
|
||
followerCountsDict[userId] =(int)count;
|
||
}
|
||
|
||
// 构建返回结果
|
||
var items = new List<object>();
|
||
foreach (var user in users)
|
||
{
|
||
items.Add(new SearchUserDto
|
||
{
|
||
UserId = user.Id,
|
||
UserName = "", // T_Users 没有 UserName 字段
|
||
NickName = user.NickName ?? "",
|
||
Avatar = user.Avatar ?? "",
|
||
IsVip = user.IsVip,
|
||
IsCertified = user.IsCertified,
|
||
Signature = user.Signature ?? "",
|
||
FollowerCount = followerCountsDict.GetValueOrDefault(user.Id, 0),
|
||
IsFollowed = currentUserId > 0 && followSet.Contains(user.Id)
|
||
});
|
||
}
|
||
|
||
var totalPages = (int)Math.Ceiling(total / (double)request.PageSize);
|
||
|
||
return new BaseResponse<SearchRespDto>(new SearchRespDto
|
||
{
|
||
PageIndex = request.PageIndex,
|
||
PageSize = request.PageSize,
|
||
Total =(int)total,
|
||
TotalPages = totalPages,
|
||
Items = items
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否关注用户
|
||
/// </summary>
|
||
/// <param name="currentUserId">当前用户ID</param>
|
||
/// <param name="targetUserId">目标用户ID</param>
|
||
/// <returns></returns>
|
||
private async Task<bool> IsFollowingUser(long currentUserId, long targetUserId)
|
||
{
|
||
if (currentUserId <= 0) return false;
|
||
|
||
var count = await _followsRepository.Select
|
||
.Where(f => f.FollowerId == currentUserId && f.FollowedUserId == targetUserId)
|
||
.CountAsync();
|
||
|
||
return count > 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取热门搜索关键词
|
||
/// </summary>
|
||
/// <param name="request">请求参数</param>
|
||
/// <returns></returns>
|
||
public async Task<BaseResponseList<HotKeywordDto>> GetHotKeywords(GetHotKeywordsReq request)
|
||
{
|
||
try
|
||
{
|
||
// 从配置中获取热门关键词
|
||
// 这是一个简化的实现,实际项目中可以从数据库或缓存中获取
|
||
var hotKeywords = new List<HotKeywordDto>
|
||
{
|
||
new HotKeywordDto { Keyword = "精彩分享", SearchCount = 1250, Rank = 1 },
|
||
new HotKeywordDto { Keyword = "技术交流", SearchCount = 980, Rank = 2 },
|
||
new HotKeywordDto { Keyword = "生活分享", SearchCount = 856, Rank = 3 },
|
||
new HotKeywordDto { Keyword = "学习心得", SearchCount = 742, Rank = 4 },
|
||
new HotKeywordDto { Keyword = "经验分享", SearchCount = 689, Rank = 5 },
|
||
new HotKeywordDto { Keyword = "问题求助", SearchCount = 567, Rank = 6 },
|
||
new HotKeywordDto { Keyword = "资源分享", SearchCount = 523, Rank = 7 },
|
||
new HotKeywordDto { Keyword = "社区活动", SearchCount = 489, Rank = 8 },
|
||
new HotKeywordDto { Keyword = "行业动态", SearchCount = 456, Rank = 9 },
|
||
new HotKeywordDto { Keyword = "创新想法", SearchCount = 423, Rank = 10 }
|
||
};
|
||
|
||
// 限制返回数量
|
||
var result = hotKeywords.Take(Math.Min(request.Limit, 20)).ToList();
|
||
|
||
return new BaseResponseList<HotKeywordDto>(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return new BaseResponseList<HotKeywordDto>(ResponseCode.Error, $"获取热门关键词失败:{ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|