using Microsoft.Extensions.Logging; using Newtonsoft.Json; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Dto.CheckIn; using QYZH.InteractiveMagazine.Models.Dto.Compensation; 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 UsersService( BaseRepository usersRepository, ILogger _logger, IPointsService pointsService, ICheckInService checkInService, ICompensationTaskService compensationTaskService, IUserJournalService userJournalService, IOperationLogService operationLogService) : BaseRepository, IUsersService { /// /// 分页查询用户列表 /// public async Task>> GetListAsync(UsersQueryInput input) { RefAsync totalNumber = 0; var list = await Queryable() .LeftJoin((u, w) => u.WxUserId == w.Id && !w.IsDeleted) .WhereIF(!string.IsNullOrEmpty(input.WxUserId), (u, w) => u.WxUserId.ToString() == input.WxUserId) .OrderBy((u, w) => u.Id, OrderByType.Desc) .Select((u, w) => new UsersOutput { Id = u.Id, WxUserId = u.WxUserId.ToString(), WxUserName = w.Name, Name = u.Name, AvatarUrl = u.AvatarUrl, Points = u.Points, Type = u.Type.ToString(), Status = u.Status.ToString(), GrowthPoints = u.GrowthPoints, UploadDomain = u.UploadDomain }) .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); var page = new PageListModel(list, input.PageIndex, input.PageSize, totalNumber); return BaseResponse>.Success(page); } /// /// 获取用户详情(仅基本信息) /// public async Task> GetDetailAsync(long id) { var user = await Queryable() .LeftJoin((u, w) => u.WxUserId == w.Id && !w.IsDeleted) .Where((u, w) => u.Id == id) .Select((u, w) => new UsersOutput { Id = u.Id, WxUserId = u.WxUserId.ToString(), WxUserName = w.Name, Name = u.Name, AvatarUrl = u.AvatarUrl, Points = u.Points, Type = u.Type.ToString(), Status = u.Status.ToString(), GrowthPoints = u.GrowthPoints, UploadDomain = u.UploadDomain }) .FirstAsync(); if (user == null) { return BaseResponse.Fail("用户不存在"); } return BaseResponse.Success(new UserDetailOutput { BasicInfo = user }); } /// /// 分页查询用户积分记录 /// public async Task>> GetUserPointsRecordsAsync(long userId, PointsRecordQueryInput input) { input.UserId = userId; var result = await pointsService.GetPointsRecordsAsync(input); return BaseResponse>.Success(result); } /// /// 分页查询用户签到记录 /// public async Task>> GetUserCheckInRecordsAsync(long userId, PageQueryModel input) { RefAsync total = 0; var records = await Context.Queryable() .Where(r => r.UserId == userId && !r.IsDeleted) .OrderBy(r => r.CheckInDate, OrderByType.Desc) .Select(r => new CheckInRecordOutput { Id = (long)r.Id, CheckInDate = r.CheckInDate, CreatedAt = r.CreatedAt, ConsecutiveDays = r.ConsecutiveDays, PointsAwarded = r.PointsAwarded, GrowthPointsAwarded = r.GrowthPointsAwarded, Type = r.Type.ToString(), Status = r.Status.ToString() }) .ToPageListAsync(input.PageIndex, input.PageSize, total); var page = new PageListModel(records, input.PageIndex, input.PageSize, total); return BaseResponse>.Success(page); } /// /// 分页查询用户补偿任务 /// public async Task>> GetUserCompensationTasksAsync(long userId, CompensationTaskQueryInput input) { RefAsync total = 0; var tasks = await Context.Queryable() .Where(t => t.UserId == userId && !t.IsDeleted) .WhereIF(input.Status.HasValue, t => t.Status == (int)input.Status) .WhereIF(input.TaskType.HasValue, t => t.TaskType == (int)input.TaskType) .WhereIF(!string.IsNullOrEmpty(input.BusinessSource), t => t.BusinessSource == input.BusinessSource) .OrderBy(t => t.CreatedAt, OrderByType.Desc) .Select(t => new CompensationTaskOutput { Id = t.Id, TaskType = (CompensationTaskTypeEnum)t.TaskType, BusinessSource = t.BusinessSource, BusinessId = t.BusinessId, UserId = t.UserId, Payload = t.Payload, ErrorMessage = t.ErrorMessage, ErrorSource = t.ErrorSource, RetryCount = t.RetryCount, MaxRetries = t.MaxRetries, Status = (CompensationTaskStatusEnum)t.Status, ProcessedAt = t.ProcessedAt, ScheduledAt = t.ScheduledAt, ResultMessage = t.ResultMessage, CreatedAt = t.CreatedAt }) .ToPageListAsync(input.PageIndex, input.PageSize, total); var page = new PageListModel(tasks, input.PageIndex, input.PageSize, total); return BaseResponse>.Success(page); } /// /// 分页查询用户期刊列表(含期刊详情) /// public async Task>> GetUserJournalsAsync(long userId, UserJournalQueryInput input) { RefAsync total = 0; var items = await Context.Queryable() .Where(uj => uj.UserId == userId && !uj.IsDeleted) .WhereIF(!string.IsNullOrWhiteSpace(input.Type), uj => uj.Type.ToString() == input.Type) .OrderByDescending(uj => uj.CreatedAt) .Select(uj => new UserJournalItemOutput { BindId = uj.Id, JournalId = uj.JournalId, Type = uj.Type.ToString(), Status = uj.Status.ToString(), CreatedAt = uj.CreatedAt }) .ToPageListAsync(input.PageIndex, input.PageSize, total); // 填充期刊详情(标题、封面) var journalIds = items.Select(i => i.JournalId).Distinct().ToList(); if (journalIds.Count > 0) { var journals = await Context.Queryable() .Where(j => journalIds.Contains(j.Id) && !j.IsDeleted) .ToListAsync(); var journalDict = journals.ToDictionary(j => j.Id); foreach (var item in items) { if (journalDict.TryGetValue(item.JournalId, out var journal)) { item.Name = journal.Name; item.CoverImageUrl = journal.Cover; } } } var page = new PageListModel(items, input.PageIndex, input.PageSize, total); return BaseResponse>.Success(page); } /// /// 更新用户状态 /// public async Task UpdateStatusAsync(long id) { var exists = await usersRepository.GetByIdAsync(id); if (exists == null) { return BaseResponse.Fail("用户不存在"); } var statusValue = exists.Status == (int)UserStatusEnum.Active ? UserStatusEnum.Disabled : UserStatusEnum.Active; var result = await UpdateAsync( u => new Users { Status = (int)statusValue }, u => u.Id == id ); return result ? BaseResponse.Success() : BaseResponse.Fail("更新失败"); } /// /// 手动增加用户积分 /// public async Task ManualAddPointsAsync(long userId, ManualAddPointsInput input, long operatorId, string operatorName, string? ipAddress = null) { _logger.LogInformation("管理员手动增加积分,UserId: {UserId}, Amount: {Amount}, Operator: {Operator}", userId, input.Amount, operatorName); // 校验用户是否存在 var user = await Queryable().Where(u => u.Id == userId && !u.IsDeleted).FirstAsync(); if (user == null) throw new BusinessException("用户不存在", ResultCode.NOT_FOUND); // 调用积分服务增加积分 var result = await pointsService.AddPointsAsync(new AddPointsInput { UserId = userId, Amount = input.Amount, ChangeType = PointsChangeTypeEnum.ManualAdjust, Description = $"管理员手动增加: {input.Reason}", OperatorName = operatorName }); // 记录操作日志 var detail = JsonConvert.SerializeObject(new { Amount = input.Amount, Reason = input.Reason, PreviousBalance = result.PreviousBalance, NewBalance = result.NewBalance, RecordId = result.RecordId }); await operationLogService.LogAsync( operatorId, operatorName, OperationLogActionType.ManualAddPoints, OperationLogTargetType.User, userId, user.Name, detail, ipAddress); return new ManualPointsOutput { RecordId = result.RecordId, PreviousBalance = result.PreviousBalance, NewBalance = result.NewBalance, ChangeAmount = input.Amount, OperatorName = operatorName, OperatedAt = DateTime.Now }; } /// /// 手动扣除用户积分 /// public async Task ManualDeductPointsAsync(long userId, ManualDeductPointsInput input, long operatorId, string operatorName, string? ipAddress = null) { _logger.LogInformation("管理员手动扣除积分,UserId: {UserId}, Amount: {Amount}, Operator: {Operator}", userId, input.Amount, operatorName); // 校验用户是否存在 var user = await Queryable().Where(u => u.Id == userId && !u.IsDeleted).FirstAsync(); if (user == null) throw new BusinessException("用户不存在", ResultCode.NOT_FOUND); // 调用积分服务扣除积分 var result = await pointsService.DeductPointsAsync(new DeductPointsInput { UserId = userId, Amount = input.Amount, ChangeType = PointsChangeTypeEnum.ManualAdjust, Description = $"管理员手动扣除: {input.Reason}", OperatorName = operatorName }); // 记录操作日志 var detail = JsonConvert.SerializeObject(new { Amount = input.Amount, Reason = input.Reason, PreviousBalance = result.PreviousBalance, NewBalance = result.NewBalance, RecordId = result.RecordId }); await operationLogService.LogAsync( operatorId, operatorName, OperationLogActionType.ManualDeductPoints, OperationLogTargetType.User, userId, user.Name, detail, ipAddress); return new ManualPointsOutput { RecordId = result.RecordId, PreviousBalance = result.PreviousBalance, NewBalance = result.NewBalance, ChangeAmount = -input.Amount, OperatorName = operatorName, OperatedAt = DateTime.Now }; } }