using MiAssessment.Core.Interfaces; using MiAssessment.Model.Data; using MiAssessment.Model.Models.Common; using MiAssessment.Model.Models.Invite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace MiAssessment.Core.Services; /// /// 分销服务实现 /// /// /// 提供分销模块的核心业务功能,包括: /// - 邀请信息查询 /// - 小程序码生成 /// - 邀请记录查询 /// - 佣金信息查询 /// - 提现记录查询 /// - 提现申请处理 /// public class InviteService : IInviteService { private readonly MiAssessmentDbContext _dbContext; private readonly ILogger _logger; private readonly IWechatService _wechatService; /// /// 构造函数 /// /// 数据库上下文 /// 日志记录器 /// 微信服务 public InviteService( MiAssessmentDbContext dbContext, ILogger logger, IWechatService wechatService) { _dbContext = dbContext; _logger = logger; _wechatService = wechatService; } /// /// /// 获取邀请信息 /// /// /// 返回用户的邀请码、余额、邀请人数等信息。 /// Requirements: 11.1 /// public async Task GetInfoAsync(long userId) { _logger.LogDebug("获取邀请信息,userId: {UserId}", userId); // 查询用户信息 var user = await _dbContext.Users .AsNoTracking() .Where(u => u.Id == userId) .Select(u => new { u.InviteCode, u.Balance, u.WithdrawnAmount, u.Uid }) .FirstOrDefaultAsync(); if (user == null) { _logger.LogWarning("用户不存在,userId: {UserId}", userId); throw new InvalidOperationException("用户不存在"); } // 统计邀请人数(直属下级) var inviteCount = await _dbContext.Users .AsNoTracking() .CountAsync(u => u.ParentUserId == userId); // 计算待提现金额(可提现余额) var pendingAmount = user.Balance; _logger.LogDebug("获取邀请信息成功,userId: {UserId}, inviteCode: {InviteCode}, inviteCount: {InviteCount}", userId, user.InviteCode, inviteCount); return new InviteInfoDto { InviteUrl = $"pages/index/index?inviter={user.Uid}", InviteCode = user.InviteCode ?? string.Empty, WithdrawnAmount = user.WithdrawnAmount, PendingAmount = pendingAmount, InviteCount = inviteCount }; } /// /// /// 生成邀请二维码 /// /// /// 调用微信小程序码接口生成带参数的二维码。 /// 二维码包含用户的邀请码参数,扫码后可识别邀请关系。 /// Requirements: 11.2 /// public async Task GetQrcodeAsync(long userId) { _logger.LogDebug("生成邀请二维码,userId: {UserId}", userId); // 查询用户信息获取邀请码 var user = await _dbContext.Users .AsNoTracking() .Where(u => u.Id == userId) .Select(u => new { u.Uid, u.InviteCode }) .FirstOrDefaultAsync(); if (user == null) { _logger.LogWarning("用户不存在,userId: {UserId}", userId); throw new InvalidOperationException("用户不存在"); } // 获取微信access_token var accessToken = await _wechatService.GetAccessTokenAsync(); if (string.IsNullOrEmpty(accessToken)) { _logger.LogWarning("获取微信access_token失败,userId: {UserId}", userId); throw new InvalidOperationException("获取微信access_token失败"); } // 生成小程序码 // 使用用户UID作为邀请参数 var scene = $"inviter={user.Uid}"; var page = "pages/index/index"; // 调用微信小程序码接口 // 这里返回一个占位URL,实际实现需要调用微信API生成二维码图片 // 并上传到OSS或返回base64 var qrcodeUrl = $"https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={accessToken}&scene={scene}&page={page}"; _logger.LogDebug("生成邀请二维码成功,userId: {UserId}, scene: {Scene}", userId, scene); return new QrcodeDto { QrcodeUrl = qrcodeUrl }; } /// /// /// 获取邀请记录列表 /// /// /// 分页查询当前用户的直属下级用户列表及其贡献的佣金。 /// Requirements: 12.1 /// public async Task> GetRecordListAsync(long userId, int page, int pageSize) { _logger.LogDebug("获取邀请记录列表,userId: {UserId}, page: {Page}, pageSize: {PageSize}", userId, page, pageSize); // 确保分页参数有效 if (page < 1) page = 1; if (pageSize < 1) pageSize = 20; if (pageSize > 100) pageSize = 100; // 查询直属下级用户(Pid = userId) var query = _dbContext.Users .AsNoTracking() .Where(u => u.ParentUserId == userId); // 获取总数 var total = await query.CountAsync(); // 分页查询下级用户 var invitedUsers = await query .OrderByDescending(u => u.CreateTime) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(u => new { u.Id, u.Nickname, u.Avatar, u.Uid, u.CreateTime }) .ToListAsync(); // 获取每个下级用户贡献的佣金总额 var userIds = invitedUsers.Select(u => u.Id).ToList(); var commissions = await _dbContext.Commissions .AsNoTracking() .Where(c => c.UserId == userId && userIds.Contains((int)c.FromUserId) && !c.IsDeleted) .GroupBy(c => c.FromUserId) .Select(g => new { FromUserId = g.Key, TotalCommission = g.Sum(c => c.CommissionAmount) }) .ToDictionaryAsync(x => x.FromUserId, x => x.TotalCommission); // 组装结果 var records = invitedUsers.Select(u => new InviteRecordDto { UserId = u.Id, Nickname = u.Nickname ?? "未设置昵称", Avatar = u.Avatar ?? string.Empty, Uid = u.Uid, RegisterDate = u.CreateTime.ToString("yyyy-MM-dd"), Commission = commissions.TryGetValue(u.Id, out var commission) ? commission : 0 }).ToList(); _logger.LogDebug("获取到 {Count} 条邀请记录,总数: {Total}", records.Count, total); return PagedResult.Create(records, total, page, pageSize); } /// /// /// 获取佣金信息 /// /// /// 返回用户的累计收益、已提现金额、待提现金额。 /// Requirements: 12.2 /// public async Task GetCommissionAsync(long userId) { _logger.LogDebug("获取佣金信息,userId: {UserId}", userId); // 查询用户信息 var user = await _dbContext.Users .AsNoTracking() .Where(u => u.Id == userId) .Select(u => new { u.TotalIncome, u.WithdrawnAmount, u.Balance }) .FirstOrDefaultAsync(); if (user == null) { _logger.LogWarning("用户不存在,userId: {UserId}", userId); throw new InvalidOperationException("用户不存在"); } _logger.LogDebug("获取佣金信息成功,userId: {UserId}, totalIncome: {TotalIncome}, withdrawnAmount: {WithdrawnAmount}, pendingAmount: {PendingAmount}", userId, user.TotalIncome, user.WithdrawnAmount, user.Balance); return new CommissionInfoDto { TotalIncome = user.TotalIncome, WithdrawnAmount = user.WithdrawnAmount, PendingAmount = user.Balance }; } /// /// /// 获取提现记录列表 /// /// /// 分页查询当前用户的提现记录。 /// Requirements: 13.5 /// public async Task> GetWithdrawListAsync(long userId, int page, int pageSize) { _logger.LogDebug("获取提现记录列表,userId: {UserId}, page: {Page}, pageSize: {PageSize}", userId, page, pageSize); // 确保分页参数有效 if (page < 1) page = 1; if (pageSize < 1) pageSize = 20; if (pageSize > 100) pageSize = 100; // 查询用户的提现记录 var query = _dbContext.Withdrawals .AsNoTracking() .Where(w => w.UserId == userId && !w.IsDeleted); // 获取总数 var total = await query.CountAsync(); // 分页查询 var withdrawals = await query .OrderByDescending(w => w.CreateTime) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(w => new WithdrawRecordDto { Id = w.Id, WithdrawalNo = w.WithdrawalNo, Amount = w.Amount, Status = w.Status, StatusText = GetWithdrawStatusText(w.Status), CreateTime = w.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"), PayTime = w.PayTime.HasValue ? w.PayTime.Value.ToString("yyyy-MM-dd HH:mm:ss") : null }) .ToListAsync(); _logger.LogDebug("获取到 {Count} 条提现记录,总数: {Total}", withdrawals.Count, total); return PagedResult.Create(withdrawals, total, page, pageSize); } /// /// /// 申请提现 /// /// /// 验证用户余额充足后创建提现记录并扣减用户余额。 /// 提现金额必须满足以下条件: /// - 金额大于等于最低限额(1元) /// - 金额必须为整数 /// - 金额不超过可提现余额 /// Requirements: 13.1, 13.2, 13.3, 13.4 /// public async Task ApplyWithdrawAsync(long userId, decimal amount) { _logger.LogDebug("申请提现,userId: {UserId}, amount: {Amount}", userId, amount); // 验证提现金额≥1元 (Requirement 13.2) if (amount < 1) { _logger.LogWarning("提现金额小于最低限额,userId: {UserId}, amount: {Amount}", userId, amount); return new ApplyWithdrawResponse { Success = false, ErrorMessage = "提现金额不能小于1元" }; } // 验证提现金额为整数 (Requirement 13.3) if (amount != Math.Floor(amount)) { _logger.LogWarning("提现金额不是整数,userId: {UserId}, amount: {Amount}", userId, amount); return new ApplyWithdrawResponse { Success = false, ErrorMessage = "提现金额必须为整数" }; } // 使用事务确保数据一致性 using var transaction = await _dbContext.Database.BeginTransactionAsync(); try { // 查询用户信息(需要锁定记录以防止并发问题) var user = await _dbContext.Users .Where(u => u.Id == userId) .FirstOrDefaultAsync(); if (user == null) { _logger.LogWarning("用户不存在,userId: {UserId}", userId); return new ApplyWithdrawResponse { Success = false, ErrorMessage = "用户不存在" }; } // 验证余额充足 (Requirement 13.4) if (amount > user.Balance) { _logger.LogWarning("提现金额超过可提现余额,userId: {UserId}, amount: {Amount}, balance: {Balance}", userId, amount, user.Balance); return new ApplyWithdrawResponse { Success = false, ErrorMessage = "超出待提现金额" }; } // 生成提现单号 (格式: W + yyyyMMdd + 6位序列号) var withdrawalNo = await GenerateWithdrawalNoAsync(); // 记录提现前余额 var beforeBalance = user.Balance; var afterBalance = beforeBalance - amount; // 创建提现记录 (Requirement 13.1) var withdrawal = new MiAssessment.Model.Entities.Withdrawal { WithdrawalNo = withdrawalNo, UserId = userId, Amount = amount, BeforeBalance = beforeBalance, AfterBalance = afterBalance, Status = 1, // 申请中 CreateTime = DateTime.Now, UpdateTime = DateTime.Now, IsDeleted = false }; _dbContext.Withdrawals.Add(withdrawal); // 扣减用户余额 (Requirement 13.1) user.Balance = afterBalance; user.UpdateTime = DateTime.Now; // 保存更改 await _dbContext.SaveChangesAsync(); // 提交事务 await transaction.CommitAsync(); _logger.LogInformation("提现申请成功,userId: {UserId}, withdrawalNo: {WithdrawalNo}, amount: {Amount}, beforeBalance: {BeforeBalance}, afterBalance: {AfterBalance}", userId, withdrawalNo, amount, beforeBalance, afterBalance); return new ApplyWithdrawResponse { Success = true, WithdrawalNo = withdrawalNo }; } catch (Exception ex) { // 回滚事务 await transaction.RollbackAsync(); _logger.LogError(ex, "提现申请失败,userId: {UserId}, amount: {Amount}", userId, amount); throw; } } /// /// 生成提现单号 /// /// /// 格式: W + yyyyMMdd + 6位序列号 /// 例如: W20240115000001 /// /// 提现单号 private async Task GenerateWithdrawalNoAsync() { var datePrefix = $"W{DateTime.Now:yyyyMMdd}"; // 查询当天最大的提现单号 var maxNo = await _dbContext.Withdrawals .AsNoTracking() .Where(w => w.WithdrawalNo.StartsWith(datePrefix)) .OrderByDescending(w => w.WithdrawalNo) .Select(w => w.WithdrawalNo) .FirstOrDefaultAsync(); int sequence = 1; if (!string.IsNullOrEmpty(maxNo) && maxNo.Length == 15) { // 提取序列号部分并加1 if (int.TryParse(maxNo.Substring(9), out var lastSequence)) { sequence = lastSequence + 1; } } return $"{datePrefix}{sequence:D6}"; } /// /// 获取提现状态文本 /// /// 状态值 /// 状态文本 private static string GetWithdrawStatusText(int status) { return status switch { 1 => "申请中", 2 => "提现中", 3 => "已提现", 4 => "已取消", _ => "未知" }; } }