using Microsoft.Extensions.Logging; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Dto.Points; using QYZH.InteractiveMagazine.Models.Entity; using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using SqlSugar; namespace QYZH.InteractiveMagazine.Service; /// /// 积分服务实现 /// public class PointsService( BaseRepository pointsRecordRepository, ILogger logger) : BaseRepository, IPointsService { #region 带事务版本(独立调用) /// /// 增加积分(带事务) /// public async Task AddPointsAsync(AddPointsInput input) { logger.LogInformation("用户增加积分,UserId: {UserId}, Amount: {Amount}, Type: {Type}", input.UserId, input.Amount, input.ChangeType); if (input.Amount <= 0) throw new BusinessException("增加积分数量必须大于0", ResultCode.BAD_REQUEST); AddPointsOutput result = null!; await UseTranAsync(async () => { result = await AddPointsInTranAsync(input); }); return result; } /// /// 扣除积分(带事务) /// public async Task DeductPointsAsync(DeductPointsInput input) { logger.LogInformation("用户扣除积分,UserId: {UserId}, Amount: {Amount}, Type: {Type}", input.UserId, input.Amount, input.ChangeType); if (input.Amount <= 0) throw new BusinessException("扣除积分数量必须大于0", ResultCode.BAD_REQUEST); DeductPointsOutput result = null!; await UseTranAsync(async () => { result = await DeductPointsInTranAsync(input); }); return result; } #endregion #region 无事务版本(供外部事务调用) /// /// 增加积分(无事务,需在外部事务中调用) /// public async Task AddPointsInTranAsync(AddPointsInput input) { if (input.Amount <= 0) throw new BusinessException("增加积分数量必须大于0", ResultCode.BAD_REQUEST); var now = DateTime.Now; // 原子增加积分,避免并发写回旧余额覆盖新余额 var affectedRows = await Context.Updateable() .SetColumns(u => u.Points == u.Points + input.Amount) .SetColumns(u => u.UpdatedAt == now) .Where(u => u.Id == input.UserId && !u.IsDeleted) .ExecuteCommandAsync(); if (affectedRows <= 0) throw new BusinessException("用户不存在", ResultCode.NOT_FOUND); var user = await Context.Queryable() .Where(u => u.Id == input.UserId && !u.IsDeleted) .FirstAsync(); if (user == null) throw new BusinessException("用户不存在", ResultCode.NOT_FOUND); var newBalance = user.Points; var previousBalance = newBalance - input.Amount; // 插入积分流水记录 var record = new PointsRecord { UserId = input.UserId, ChangeAmount = input.Amount, BalanceAfter = newBalance, ChangeType = input.ChangeType.ToString(), RelatedId = input.RelatedId, Description = input.Description, Type = PointsFlowTypeEnum.Income, Status = (int)PointsRecordStatusEnum.Success, IsDeleted = false, CreatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(), CreatedAt = now, UpdatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(), UpdatedAt = now }; var recordEntity = await InsertReturnEntityAsync(record); logger.LogInformation("增加积分成功,UserId: {UserId}, 积分: {Before} -> {After}, 变动: +{Amount}", input.UserId, previousBalance, newBalance, input.Amount); return new AddPointsOutput { RecordId = recordEntity.Id, PreviousBalance = previousBalance, NewBalance = newBalance, AddedAmount = input.Amount }; } /// /// 扣除积分(无事务,需在外部事务中调用) /// public async Task DeductPointsInTranAsync(DeductPointsInput input) { if (input.Amount <= 0) throw new BusinessException("扣除积分数量必须大于0", ResultCode.BAD_REQUEST); var now = DateTime.Now; // 带余额条件的原子扣减,避免并发扣减时超扣或覆盖余额 var affectedRows = await Context.Updateable() .SetColumns(u => u.Points == u.Points - input.Amount) .SetColumns(u => u.UpdatedAt == now) .Where(u => u.Id == input.UserId && !u.IsDeleted && u.Points >= input.Amount) .ExecuteCommandAsync(); if (affectedRows <= 0) { var currentUser = await Context.Queryable() .Where(u => u.Id == input.UserId && !u.IsDeleted) .FirstAsync(); if (currentUser == null) throw new BusinessException("用户不存在", ResultCode.NOT_FOUND); throw new BusinessException($"积分不足,需要 {input.Amount} 积分,当前余额 {currentUser.Points}", ResultCode.BAD_REQUEST); } var user = await Context.Queryable() .Where(u => u.Id == input.UserId && !u.IsDeleted) .FirstAsync(); if (user == null) throw new BusinessException("用户不存在", ResultCode.NOT_FOUND); var newBalance = user.Points; var previousBalance = newBalance + input.Amount; // 插入积分流水记录 var record = new PointsRecord { UserId = input.UserId, ChangeAmount = -input.Amount, BalanceAfter = newBalance, ChangeType = input.ChangeType.ToString(), RelatedId = input.RelatedId, Description = input.Description, Type = PointsFlowTypeEnum.Expense, Status = (int)PointsRecordStatusEnum.Success, IsDeleted = false, CreatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(), CreatedAt = now, UpdatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(), UpdatedAt = now }; var recordEntity = await InsertReturnEntityAsync(record); logger.LogInformation("扣除积分成功,UserId: {UserId}, 积分: {Before} -> {After}, 变动: -{Amount}", input.UserId, previousBalance, newBalance, input.Amount); return new DeductPointsOutput { RecordId = recordEntity.Id, PreviousBalance = previousBalance, NewBalance = newBalance, DeductedAmount = input.Amount }; } #endregion #region 查询 /// /// 查询用户当前积分余额 /// public async Task GetUserPointsAsync(long userId) { var user = await Context.Queryable() .Where(u => u.Id == userId && !u.IsDeleted) .Select(u => u.Points) .FirstAsync(); return user; } /// /// 获取用户积分概览 /// public async Task GetPointsSummaryAsync(long userId) { // 查询用户 var user = await Context.Queryable() .Where(u => u.Id == userId && !u.IsDeleted) .FirstAsync(); if (user == null) throw new BusinessException("用户不存在", ResultCode.NOT_FOUND); // 查询累计收入(Income 类型) var totalIncome = await Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted && r.Type == PointsFlowTypeEnum.Income && r.Status == (int)PointsRecordStatusEnum.Success) .SumAsync(r => r.ChangeAmount); // 查询累计支出(Expense 类型,取绝对值) var totalExpense = await Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted && r.Type == PointsFlowTypeEnum.Expense && r.Status == (int)PointsRecordStatusEnum.Success) .SumAsync(r => r.ChangeAmount); return new PointsSummaryOutput { UserId = userId, CurrentBalance = user.Points, TotalIncome = totalIncome, TotalExpense = Math.Abs(totalExpense) }; } /// /// 分页查询积分流水 /// public async Task> GetPointsRecordsAsync(PointsRecordQueryInput input) { if (input.PageIndex <= 0) throw new BusinessException("页码必须大于0", ResultCode.BAD_REQUEST); if (input.PageSize <= 0 || input.PageSize > 100) throw new BusinessException("每页条数必须在1-100之间", ResultCode.BAD_REQUEST); var query = Context.Queryable() .Where(r => r.UserId == input.UserId && !r.IsDeleted) .WhereIF(input.ChangeType.HasValue, r => r.ChangeType == input.ChangeType.Value.ToString()) .WhereIF(input.Type.HasValue, r => r.Type == input.Type.Value) .WhereIF(!string.IsNullOrEmpty(input.Status), r => r.Status.ToString() == input.Status) .OrderByDescending(r => r.CreatedAt); RefAsync total = 0; var records = await query .Select(r => new PointsRecordOutput { Id = (long)r.Id, ChangeAmount = r.ChangeAmount, BalanceAfter = r.BalanceAfter, ChangeType = r.ChangeType, Description = r.Description, Type = r.Type.ToString(), CreatedAt = r.CreatedAt }) .ToPageListAsync(input.PageIndex, input.PageSize, total); return new PageListModel(records, input.PageIndex, input.PageSize, total); } #endregion }