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 => "已取消",
_ => "未知"
};
}
}