using Microsoft.Extensions.Logging; using XiangYi.Application.DTOs.Requests; using XiangYi.Application.DTOs.Responses; using XiangYi.Application.Interfaces; using XiangYi.Core.Constants; using XiangYi.Core.Entities.Biz; using XiangYi.Core.Exceptions; using XiangYi.Core.Interfaces; namespace XiangYi.Application.Services; /// /// 后台用户管理服务实现 /// public class AdminUserService : IAdminUserService { private readonly IRepository _userRepository; private readonly IRepository _profileRepository; private readonly IRepository _photoRepository; private readonly IRepository _requirementRepository; private readonly IRepository _orderRepository; private readonly ILogger _logger; public AdminUserService( IRepository userRepository, IRepository profileRepository, IRepository photoRepository, IRepository requirementRepository, IRepository orderRepository, ILogger logger) { _userRepository = userRepository; _profileRepository = profileRepository; _photoRepository = photoRepository; _requirementRepository = requirementRepository; _orderRepository = orderRepository; _logger = logger; } /// public async Task> GetUserListAsync(AdminUserQueryRequest request) { // Build query predicate var users = await _userRepository.GetAllAsync(); var query = users.AsQueryable(); // Filter by keyword (XiangQinNo, Nickname, Phone) if (!string.IsNullOrWhiteSpace(request.Keyword)) { var keyword = request.Keyword.Trim(); query = query.Where(u => (u.XiangQinNo != null && u.XiangQinNo.Contains(keyword)) || (u.Nickname != null && u.Nickname.Contains(keyword)) || (u.Phone != null && u.Phone.Contains(keyword))); } // Filter by status if (request.Status.HasValue) { query = query.Where(u => u.Status == request.Status.Value); } // Filter by member status if (request.IsMember.HasValue) { query = query.Where(u => u.IsMember == request.IsMember.Value); } // Filter by member level if (request.MemberLevel.HasValue) { query = query.Where(u => u.MemberLevel == request.MemberLevel.Value); } // Filter by real name status if (request.IsRealName.HasValue) { query = query.Where(u => u.IsRealName == request.IsRealName.Value); } // Filter by profile completed status if (request.IsProfileCompleted.HasValue) { query = query.Where(u => u.IsProfileCompleted == request.IsProfileCompleted.Value); } // Filter by gender if (request.Gender.HasValue) { query = query.Where(u => u.Gender == request.Gender.Value); } // Filter by city if (!string.IsNullOrWhiteSpace(request.City)) { query = query.Where(u => u.City != null && u.City.Contains(request.City)); } // Filter by register time range if (request.RegisterStartTime.HasValue) { query = query.Where(u => u.CreateTime >= request.RegisterStartTime.Value); } if (request.RegisterEndTime.HasValue) { var endTime = request.RegisterEndTime.Value.Date.AddDays(1); query = query.Where(u => u.CreateTime < endTime); } // Exclude soft deleted users query = query.Where(u => !u.IsDeleted); // Get total count var total = query.Count(); // Order by CreateTime descending and paginate var items = query .OrderByDescending(u => u.CreateTime) .Skip((request.PageIndex - 1) * request.PageSize) .Take(request.PageSize) .ToList(); // Get user IDs for profile audit status lookup var userIds = items.Select(u => u.Id).ToList(); var profiles = await _profileRepository.GetListAsync(p => userIds.Contains(p.UserId)); var profileDict = profiles.ToDictionary(p => p.UserId); // Filter by audit status if specified if (request.AuditStatus.HasValue) { var filteredUserIds = profiles .Where(p => p.AuditStatus == request.AuditStatus.Value) .Select(p => p.UserId) .ToHashSet(); items = items.Where(u => filteredUserIds.Contains(u.Id)).ToList(); total = items.Count; } // Map to DTOs var dtos = items.Select(u => { profileDict.TryGetValue(u.Id, out var profile); return MapToUserListDto(u, profile); }).ToList(); return new PagedResult { Items = dtos, Total = total, PageIndex = request.PageIndex, PageSize = request.PageSize }; } /// public async Task GetUserDetailAsync(long userId) { var user = await _userRepository.GetByIdAsync(userId); if (user == null || user.IsDeleted) { throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在"); } // Get profile var profiles = await _profileRepository.GetListAsync(p => p.UserId == userId); var profile = profiles.FirstOrDefault(); // Get photos var photos = await _photoRepository.GetListAsync(p => p.UserId == userId); // Get requirement var requirements = await _requirementRepository.GetListAsync(r => r.UserId == userId); var requirement = requirements.FirstOrDefault(); // Get orders var orders = await _orderRepository.GetListAsync(o => o.UserId == userId); return MapToUserDetailDto(user, profile, photos, requirement, orders); } /// public async Task UpdateUserStatusAsync(long userId, int status, long adminId) { var user = await _userRepository.GetByIdAsync(userId); if (user == null || user.IsDeleted) { throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在"); } // Validate status value if (status != 1 && status != 2) { throw new BusinessException(ErrorCodes.InvalidParameter, "无效的状态值"); } user.Status = status; user.UpdateTime = DateTime.Now; var result = await _userRepository.UpdateAsync(user); _logger.LogInformation("管理员更新用户状态: AdminId={AdminId}, UserId={UserId}, Status={Status}", adminId, userId, status); return result > 0; } /// public async Task GetUserStatisticsAsync() { var now = DateTime.Now; var today = now.Date; var weekStart = today.AddDays(-(int)today.DayOfWeek); var monthStart = new DateTime(now.Year, now.Month, 1); // Get all users (excluding soft deleted) var allUsers = await _userRepository.GetListAsync(u => !u.IsDeleted); // Total users var totalUsers = allUsers.Count; // Today new users var todayNewUsers = allUsers.Count(u => u.CreateTime >= today); // Week new users var weekNewUsers = allUsers.Count(u => u.CreateTime >= weekStart); // Month new users var monthNewUsers = allUsers.Count(u => u.CreateTime >= monthStart); // Total members var totalMembers = allUsers.Count(u => u.IsMember); // Today new members (users who became members today) var todayNewMembers = allUsers.Count(u => u.IsMember && u.UpdateTime >= today); // Real name users var realNameUsers = allUsers.Count(u => u.IsRealName); // Profile completed users var profileCompletedUsers = allUsers.Count(u => u.IsProfileCompleted); // Pending audit profiles var profiles = await _profileRepository.GetListAsync(p => p.AuditStatus == 0); var pendingAuditProfiles = profiles.Count; // Today active users (logged in today) var todayActiveUsers = allUsers.Count(u => u.LastLoginTime >= today); // Week active users var weekActiveUsers = allUsers.Count(u => u.LastLoginTime >= weekStart); // Month active users var monthActiveUsers = allUsers.Count(u => u.LastLoginTime >= monthStart); // Disabled users var disabledUsers = allUsers.Count(u => u.Status == 2); // Member level statistics var memberLevelStats = allUsers .Where(u => u.IsMember) .GroupBy(u => u.MemberLevel) .Select(g => new MemberLevelStatDto { MemberLevel = g.Key, MemberLevelText = GetMemberLevelText(g.Key), Count = g.Count() }) .OrderBy(s => s.MemberLevel) .ToList(); // User growth trend (last 7 days) var userGrowthTrend = new List(); for (int i = 6; i >= 0; i--) { var date = today.AddDays(-i); var nextDate = date.AddDays(1); var count = allUsers.Count(u => u.CreateTime >= date && u.CreateTime < nextDate); userGrowthTrend.Add(new DailyStatDto { Date = date.ToString("MM-dd"), Count = count }); } return new AdminUserStatisticsDto { TotalUsers = totalUsers, TodayNewUsers = todayNewUsers, WeekNewUsers = weekNewUsers, MonthNewUsers = monthNewUsers, TotalMembers = totalMembers, TodayNewMembers = todayNewMembers, RealNameUsers = realNameUsers, ProfileCompletedUsers = profileCompletedUsers, PendingAuditProfiles = pendingAuditProfiles, TodayActiveUsers = todayActiveUsers, WeekActiveUsers = weekActiveUsers, MonthActiveUsers = monthActiveUsers, DisabledUsers = disabledUsers, MemberLevelStats = memberLevelStats, UserGrowthTrend = userGrowthTrend }; } /// public async Task> CreateTestUsersAsync(int count, int? gender = null) { var createdIds = new List(); var random = new Random(); // 测试数据池 var maleNames = new[] { "张", "李", "王", "刘", "陈", "杨", "赵", "黄", "周", "吴", "徐", "孙", "胡", "朱", "高" }; var femaleNames = new[] { "林", "何", "郭", "马", "罗", "梁", "宋", "郑", "谢", "韩", "唐", "冯", "于", "董", "萧" }; var cities = new[] { "北京", "上海", "广州", "深圳", "杭州", "成都", "武汉", "南京", "苏州", "西安" }; var provinces = new[] { "北京", "上海", "广东", "广东", "浙江", "四川", "湖北", "江苏", "江苏", "陕西" }; var occupations = new[] { "程序员", "设计师", "产品经理", "教师", "医生", "律师", "会计", "销售", "工程师", "公务员" }; var introductions = new[] { "性格开朗,喜欢运动和旅行", "工作稳定,希望找到志同道合的另一半", "热爱生活,喜欢美食和电影", "踏实上进,有责任心", "温柔体贴,顾家爱家" }; for (int i = 0; i < count; i++) { try { // 确定性别 var userGender = gender ?? (random.Next(2) + 1); var surnames = userGender == 1 ? maleNames : femaleNames; var surname = surnames[random.Next(surnames.Length)]; // 生成相亲编号 var xiangQinNo = $"{random.Next(100000, 999999)}"; // 生成用户 var cityIndex = random.Next(cities.Length); var user = new User { OpenId = $"test_openid_{Guid.NewGuid():N}", Nickname = $"{surname}先生" + (userGender == 1 ? "" : "女士").Replace("先生女士", "女士"), XiangQinNo = xiangQinNo, Gender = userGender, City = cities[cityIndex], Status = 1, IsProfileCompleted = true, IsRealName = random.Next(2) == 1, IsMember = random.Next(3) == 0, // 1/3概率是会员 MemberLevel = 0, ContactCount = 2, CreateTime = DateTime.Now.AddDays(-random.Next(30)), UpdateTime = DateTime.Now, LastLoginTime = DateTime.Now.AddHours(-random.Next(72)) }; // 如果是会员,设置会员等级 if (user.IsMember) { user.MemberLevel = random.Next(1, 4); if (user.MemberLevel != 1) // 非不限时会员 { user.MemberExpireTime = DateTime.Now.AddMonths(random.Next(1, 12)); } } // 修正昵称 user.Nickname = userGender == 1 ? $"{surname}先生" : $"{surname}女士"; await _userRepository.AddAsync(user); // 生成用户资料 var birthYear = DateTime.Now.Year - random.Next(23, 36); var profile = new UserProfile { UserId = user.Id, Relationship = random.Next(1, 4), // 1父亲 2母亲 3本人 Surname = surname, ChildGender = userGender, BirthYear = birthYear, Education = random.Next(3, 7), // 3大专 4本科 5研究生 6博士 WorkProvince = provinces[cityIndex], WorkCity = cities[cityIndex], Occupation = occupations[random.Next(occupations.Length)], MonthlyIncome = random.Next(2, 5), // 2:5k-1w 3:1w-2w 4:2w-5w Height = userGender == 1 ? random.Next(168, 186) : random.Next(158, 172), Weight = userGender == 1 ? random.Next(60, 85) : random.Next(45, 65), HouseStatus = random.Next(1, 6), CarStatus = random.Next(1, 4), MarriageStatus = random.Next(3) == 0 ? random.Next(1, 4) : 1, // 大部分未婚 ExpectMarryTime = random.Next(1, 4), Introduction = introductions[random.Next(introductions.Length)], IsPhotoPublic = true, HomeProvince = provinces[random.Next(provinces.Length)], HomeCity = cities[random.Next(cities.Length)], WeChatNo = $"wx_test_{random.Next(100000, 999999)}", AuditStatus = 1, // 已通过审核 AuditTime = DateTime.Now, CreateTime = user.CreateTime, UpdateTime = DateTime.Now }; await _profileRepository.AddAsync(profile); createdIds.Add(user.Id); _logger.LogInformation("创建测试用户成功: UserId={UserId}, Nickname={Nickname}", user.Id, user.Nickname); } catch (Exception ex) { _logger.LogError(ex, "创建测试用户失败"); } } return createdIds; } /// public async Task DeleteUserAsync(long userId, long adminId) { var user = await _userRepository.GetByIdAsync(userId); if (user == null) { throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在"); } // 删除用户相关数据 await _photoRepository.DeleteAsync(p => p.UserId == userId); await _requirementRepository.DeleteAsync(r => r.UserId == userId); await _profileRepository.DeleteAsync(p => p.UserId == userId); await _userRepository.DeleteAsync(userId); _logger.LogInformation("管理员删除用户: AdminId={AdminId}, UserId={UserId}, Nickname={Nickname}", adminId, userId, user.Nickname); return true; } #region Private Helper Methods private static AdminUserListDto MapToUserListDto(User user, UserProfile? profile) { return new AdminUserListDto { UserId = user.Id, XiangQinNo = user.XiangQinNo, Nickname = user.Nickname, Avatar = user.Avatar, Phone = MaskPhone(user.Phone), Gender = user.Gender, GenderText = GetGenderText(user.Gender), City = profile?.WorkCity ?? user.City, Status = user.Status, StatusText = GetStatusText(user.Status), IsProfileCompleted = user.IsProfileCompleted, IsRealName = user.IsRealName, IsMember = user.IsMember, MemberLevel = user.MemberLevel, MemberLevelText = GetMemberLevelText(user.MemberLevel), AuditStatus = profile?.AuditStatus, AuditStatusText = profile != null ? GetAuditStatusText(profile.AuditStatus) : null, ContactCount = user.ContactCount, CreateTime = user.CreateTime, LastLoginTime = user.LastLoginTime }; } private static AdminUserDetailDto MapToUserDetailDto( User user, UserProfile? profile, List photos, UserRequirement? requirement, List orders) { return new AdminUserDetailDto { UserId = user.Id, XiangQinNo = user.XiangQinNo, Nickname = user.Nickname, Avatar = user.Avatar, Phone = MaskPhone(user.Phone), OpenId = user.OpenId, Gender = user.Gender, GenderText = GetGenderText(user.Gender), City = user.City, Status = user.Status, StatusText = GetStatusText(user.Status), IsProfileCompleted = user.IsProfileCompleted, IsRealName = user.IsRealName, IsMember = user.IsMember, MemberLevel = user.MemberLevel, MemberLevelText = GetMemberLevelText(user.MemberLevel), MemberExpireTime = user.MemberExpireTime, ContactCount = user.ContactCount, CreateTime = user.CreateTime, LastLoginTime = user.LastLoginTime, Profile = profile != null ? MapToProfileDto(profile) : null, Photos = photos.OrderBy(p => p.Sort).Select(MapToPhotoDto).ToList(), Requirement = requirement != null ? MapToRequirementDto(requirement) : null, Orders = orders.OrderByDescending(o => o.CreateTime).Select(MapToOrderDto).ToList() }; } private static AdminUserProfileDto MapToProfileDto(UserProfile profile) { return new AdminUserProfileDto { ProfileId = profile.Id, Relationship = profile.Relationship, RelationshipText = GetRelationshipText(profile.Relationship), Surname = profile.Surname, ChildGender = profile.ChildGender, ChildGenderText = GetGenderText(profile.ChildGender) ?? string.Empty, BirthYear = profile.BirthYear, Age = DateTime.Now.Year - profile.BirthYear, Education = profile.Education, EducationText = GetEducationText(profile.Education), WorkProvince = profile.WorkProvince, WorkCity = profile.WorkCity, WorkDistrict = profile.WorkDistrict, Occupation = profile.Occupation, MonthlyIncome = profile.MonthlyIncome, MonthlyIncomeText = GetMonthlyIncomeText(profile.MonthlyIncome), Height = profile.Height, Weight = profile.Weight, HouseStatus = profile.HouseStatus, HouseStatusText = GetHouseStatusText(profile.HouseStatus), CarStatus = profile.CarStatus, CarStatusText = GetCarStatusText(profile.CarStatus), MarriageStatus = profile.MarriageStatus, MarriageStatusText = GetMarriageStatusText(profile.MarriageStatus), ExpectMarryTime = profile.ExpectMarryTime, ExpectMarryTimeText = GetExpectMarryTimeText(profile.ExpectMarryTime), Introduction = profile.Introduction, IsPhotoPublic = profile.IsPhotoPublic, HomeProvince = profile.HomeProvince, HomeCity = profile.HomeCity, WeChatNo = profile.WeChatNo, AuditStatus = profile.AuditStatus, AuditStatusText = GetAuditStatusText(profile.AuditStatus), AuditTime = profile.AuditTime, RejectReason = profile.RejectReason, CreateTime = profile.CreateTime, UpdateTime = profile.UpdateTime }; } private static AdminUserPhotoDto MapToPhotoDto(UserPhoto photo) { return new AdminUserPhotoDto { PhotoId = photo.Id, PhotoUrl = photo.PhotoUrl, IsMain = photo.Sort == 0, Sort = photo.Sort, CreateTime = photo.CreateTime }; } private static AdminUserRequirementDto MapToRequirementDto(UserRequirement requirement) { return new AdminUserRequirementDto { RequirementId = requirement.Id, AgeMin = requirement.AgeMin, AgeMax = requirement.AgeMax, HeightMin = requirement.HeightMin, HeightMax = requirement.HeightMax, Education = requirement.Education, WorkCity = $"{requirement.City1Province}{requirement.City1City}" + (string.IsNullOrEmpty(requirement.City2Province) ? "" : $", {requirement.City2Province}{requirement.City2City}"), IncomeMin = requirement.MonthlyIncomeMin, IncomeMax = requirement.MonthlyIncomeMax, MarriageStatus = requirement.MarriageStatus, RequireHouse = !string.IsNullOrEmpty(requirement.HouseStatus), RequireCar = !string.IsNullOrEmpty(requirement.CarStatus) }; } private static AdminUserOrderDto MapToOrderDto(Order order) { return new AdminUserOrderDto { OrderId = order.Id, OrderNo = order.OrderNo, OrderType = order.OrderType, OrderTypeText = GetOrderTypeText(order.OrderType), ProductName = order.ProductName, Amount = order.Amount, PayAmount = order.PayAmount, Status = order.Status, StatusText = GetOrderStatusText(order.Status), PayTime = order.PayTime, CreateTime = order.CreateTime }; } /// /// 手机号脱敏 /// private static string? MaskPhone(string? phone) { if (string.IsNullOrEmpty(phone) || phone.Length < 7) { return phone; } return $"{phone[..3]}****{phone[^4..]}"; } /// /// 获取性别文本 /// public static string? GetGenderText(int? gender) { return gender switch { 1 => "男", 2 => "女", _ => null }; } /// /// 获取状态文本 /// public static string GetStatusText(int status) { return status switch { 1 => "正常", 2 => "禁用", _ => "未知" }; } /// /// 获取会员等级文本 /// public static string GetMemberLevelText(int memberLevel) { return memberLevel switch { 0 => "非会员", 1 => "不限时会员", 2 => "诚意会员", 3 => "家庭版会员", _ => "未知" }; } /// /// 获取审核状态文本 /// public static string GetAuditStatusText(int auditStatus) { return auditStatus switch { 0 => "待审核", 1 => "已通过", 2 => "已拒绝", _ => "未知" }; } /// /// 获取关系文本 /// public static string GetRelationshipText(int relationship) { return relationship switch { 1 => "父亲", 2 => "母亲", 3 => "本人", _ => "未知" }; } /// /// 获取学历文本 /// public static string GetEducationText(int education) { return education switch { 0 => "不限", 1 => "高中", 2 => "中专", 3 => "大专", 4 => "本科", 5 => "研究生", 6 => "博士及以上", _ => "未知" }; } /// /// 获取月收入文本 /// public static string GetMonthlyIncomeText(int monthlyIncome) { return monthlyIncome switch { 1 => "5000以下", 2 => "5000-10000", 3 => "10000-20000", 4 => "20000-50000", 5 => "50000以上", _ => "未知" }; } /// /// 获取房产情况文本 /// public static string GetHouseStatusText(int houseStatus) { return houseStatus switch { 1 => "现居地已购房", 2 => "家乡已购房", 3 => "婚后购房", 4 => "父母同住", 5 => "租房", 6 => "近期有购房计划", _ => "未知" }; } /// /// 获取车辆情况文本 /// public static string GetCarStatusText(int carStatus) { return carStatus switch { 1 => "已购车", 2 => "无车", 3 => "近期购车", _ => "未知" }; } /// /// 获取婚姻状态文本 /// public static string GetMarriageStatusText(int marriageStatus) { return marriageStatus switch { 1 => "未婚", 2 => "离异未育", 3 => "离异已育", _ => "未知" }; } /// /// 获取期望结婚时间文本 /// public static string GetExpectMarryTimeText(int expectMarryTime) { return expectMarryTime switch { 1 => "尽快结婚", 2 => "一到两年内", 3 => "孩子满意就结婚", _ => "未知" }; } /// /// 获取订单类型文本 /// public static string GetOrderTypeText(int orderType) { return orderType switch { 1 => "会员", 2 => "实名认证", _ => "未知" }; } /// /// 获取订单状态文本 /// public static string GetOrderStatusText(int status) { return status switch { 1 => "待支付", 2 => "已支付", 3 => "已取消", 4 => "已退款", _ => "未知" }; } #endregion }