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
}