feat: 完成多模块功能迭代与优化

1.  新增宠物皮肤初始枚举值、下拉列表接口及相关DTO
2.  完善商品实体与DTO,新增虚拟商品标记和类型ID字段
3.  重构用户认证体系,支持多用户切换并重新生成JWT令牌
4.  新增签到配置管理全套功能,包括增删改查和状态管理
5.  优化模型验证过滤器和基础响应类的命名规范
6.  新增补签功能,完善签到服务逻辑
7.  拆分用户详情DTO,新增各子数据分页查询接口
8.  重构微信控制器的用户ID获取逻辑,统一使用激活用户ID
9.  修复背包服务中补签卡的扣减逻辑
10. 新增家长姓名修改接口和相关服务实现
This commit is contained in:
glz
2026-06-10 17:53:38 +08:00
parent 028e3dfb34
commit ae4cd627df
35 changed files with 1334 additions and 316 deletions

View File

@ -37,7 +37,7 @@ public class UsersService(
}
/// <summary>
/// 获取用户详情(包含积分记录、签到记录、补偿任务、期刊列表
/// 获取用户详情(仅基本信息
/// </summary>
public async Task<BaseResponse<UserDetailOutput>> GetDetailAsync(long id)
{
@ -47,125 +47,124 @@ public class UsersService(
return BaseResponse<UserDetailOutput>.Fail("用户不存在");
}
// 并行查询关联数据
var pointsTask = GetPointsRecordsAsync(id);
var checkInTask = GetCheckInRecordsAsync(id);
var compensationTask = GetFailedCompensationTasksAsync(id);
var journalsTask = GetUserJournalsWithDetailAsync(id);
await Task.WhenAll(pointsTask, checkInTask, compensationTask, journalsTask);
return BaseResponse<UserDetailOutput>.Success(new UserDetailOutput
{
BasicInfo = user,
PointsRecords = await pointsTask,
CheckInRecords = await checkInTask,
FailedCompensationTasks = await compensationTask,
Journals = await journalsTask
BasicInfo = user
});
}
/// <summary>
/// 获取用户积分记录最近20条
/// 分页查询用户积分记录
/// </summary>
private async Task<List<PointsRecordOutput>> GetPointsRecordsAsync(long userId)
public async Task<BaseResponse<PageListModel<PointsRecordOutput>>> GetUserPointsRecordsAsync(long userId, PointsRecordQueryInput input)
{
try
{
var result = await pointsService.GetPointsRecordsAsync(new PointsRecordQueryInput
input.UserId = userId;
var result = await pointsService.GetPointsRecordsAsync(input);
return BaseResponse<PageListModel<PointsRecordOutput>>.Success(result);
}
/// <summary>
/// 分页查询用户签到记录
/// </summary>
public async Task<BaseResponse<PageListModel<CheckInRecordOutput>>> GetUserCheckInRecordsAsync(long userId, PageQueryModel input)
{
RefAsync<int> total = 0;
var records = await Context.Queryable<CheckInRecord>()
.Where(r => r.UserId == userId && !r.IsDeleted)
.OrderBy(r => r.CheckInDate, OrderByType.Desc)
.Select(r => new CheckInRecordOutput
{
UserId = userId,
PageIndex = 1,
PageSize = 20
});
return result.Result ?? [];
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取用户积分记录失败UserId: {UserId}", userId);
return [];
}
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<CheckInRecordOutput>(records, input.PageIndex, input.PageSize, total);
return BaseResponse<PageListModel<CheckInRecordOutput>>.Success(page);
}
/// <summary>
/// 获取用户签到记录
/// 分页查询用户补偿任务
/// </summary>
private async Task<List<CheckInRecordOutput>> GetCheckInRecordsAsync(long userId)
public async Task<BaseResponse<PageListModel<CompensationTaskOutput>>> GetUserCompensationTasksAsync(long userId, CompensationTaskQueryInput input)
{
try
{
var info = await checkInService.GetCheckInInfoAsync(userId);
return info.RecentRecords ?? [];
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取用户签到记录失败UserId: {UserId}", userId);
return [];
}
}
/// <summary>
/// 获取用户失败的补偿任务(需要手动处理)
/// </summary>
private async Task<List<CompensationTaskOutput>> GetFailedCompensationTasksAsync(long userId)
{
try
{
var tasks = await compensationTaskService.GetTasksAsync(new GetCompensationTasksInput
RefAsync<int> total = 0;
var tasks = await Context.Queryable<CompensationTask>()
.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
{
UserId = userId,
Status = CompensationTaskStatusEnum.Failed,
Limit = 20
});
return tasks;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取用户失败补偿任务失败UserId: {UserId}", userId);
return [];
}
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<CompensationTaskOutput>(tasks, input.PageIndex, input.PageSize, total);
return BaseResponse<PageListModel<CompensationTaskOutput>>.Success(page);
}
/// <summary>
/// 获取用户拥有的期刊列表(含期刊详情)
/// 分页查询用户期刊列表(含期刊详情)
/// </summary>
private async Task<List<UserJournalItemOutput>> GetUserJournalsWithDetailAsync(long userId)
public async Task<BaseResponse<PageListModel<UserJournalItemOutput>>> GetUserJournalsAsync(long userId, UserJournalQueryInput input)
{
try
RefAsync<int> total = 0;
var items = await Context.Queryable<UserJournal>()
.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 userJournals = await Context.Queryable<UserJournal>()
.Where(uj => uj.UserId == userId && !uj.IsDeleted)
.OrderByDescending(uj => uj.CreatedAt)
var journals = await Context.Queryable<Journal>()
.Where(j => journalIds.Contains(j.Id) && !j.IsDeleted)
.ToListAsync();
var result = new List<UserJournalItemOutput>();
foreach (var uj in userJournals)
var journalDict = journals.ToDictionary(j => j.Id);
foreach (var item in items)
{
var journal = await Context.Queryable<Journal>()
.Where(j => j.Id == uj.JournalId && !j.IsDeleted)
.FirstAsync();
if (journal != null)
if (journalDict.TryGetValue(item.JournalId, out var journal))
{
result.Add(new UserJournalItemOutput
{
BindId = uj.Id,
JournalId = uj.JournalId,
JournalTitle = journal.Title,
CoverImageUrl = journal.CoverImageUrl,
Type = uj.Type.ToString(),
Status = uj.Status.ToString(),
CreatedAt = uj.CreatedAt
});
item.JournalTitle = journal.Title;
item.CoverImageUrl = journal.CoverImageUrl;
}
}
return result;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取用户期刊列表失败UserId: {UserId}", userId);
return [];
}
var page = new PageListModel<UserJournalItemOutput>(items, input.PageIndex, input.PageSize, total);
return BaseResponse<PageListModel<UserJournalItemOutput>>.Success(page);
}
/// <summary>