diff --git a/QYZH.InteractiveMagazine.IService/ICommunityMessageService.cs b/QYZH.InteractiveMagazine.IService/ICommunityMessageService.cs index 8a2b896..10e60aa 100644 --- a/QYZH.InteractiveMagazine.IService/ICommunityMessageService.cs +++ b/QYZH.InteractiveMagazine.IService/ICommunityMessageService.cs @@ -41,4 +41,10 @@ public interface ICommunityMessageService : IBaseService /// 设置排序权重 /// Task SetSortOrderAsync(long id, int sortOrder); + + /// + /// 批量发布消息(IsActive false => true) + /// + /// 消息ID列表 + Task BatchPublishAsync(List ids); } diff --git a/QYZH.InteractiveMagazine.IService/ICompensationManageService.cs b/QYZH.InteractiveMagazine.IService/ICompensationManageService.cs new file mode 100644 index 0000000..018b9f0 --- /dev/null +++ b/QYZH.InteractiveMagazine.IService/ICompensationManageService.cs @@ -0,0 +1,45 @@ +using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Dto.Compensation; +using QYZH.InteractiveMagazine.Models.Entity; + +namespace QYZH.InteractiveMagazine.IService; + +/// +/// 补偿任务管理服务接口 +/// +public interface ICompensationManageService : IBaseService +{ + /// + /// 分页查询补偿任务 + /// + /// 查询参数 + /// 分页结果 + Task> GetListAsync(CompensationTaskQueryInput input); + + /// + /// 手动重试补偿任务(将状态改为 Pending,重置 RetryCount) + /// + /// 任务Id + /// 操作人Id + /// 操作人用户名 + /// 重试参数 + /// IP地址 + Task RetryAsync(long taskId, long operatorId, string operatorName, CompensationRetryInput input, string? ipAddress = null); + + /// + /// 标记补偿任务为已解决 + /// + /// 任务Id + /// 操作人Id + /// 操作人用户名 + /// 解决参数 + /// IP地址 + Task ResolveAsync(long taskId, long operatorId, string operatorName, CompensationResolveInput input, string? ipAddress = null); + + /// + /// 获取补偿任务详情 + /// + /// 任务Id + /// 任务详情 + Task GetDetailAsync(long taskId); +} diff --git a/QYZH.InteractiveMagazine.IService/IOperationLogService.cs b/QYZH.InteractiveMagazine.IService/IOperationLogService.cs new file mode 100644 index 0000000..06ba847 --- /dev/null +++ b/QYZH.InteractiveMagazine.IService/IOperationLogService.cs @@ -0,0 +1,37 @@ +using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Entity; + +namespace QYZH.InteractiveMagazine.IService; + +/// +/// 操作日志服务接口 +/// +public interface IOperationLogService : IBaseService +{ + /// + /// 记录操作日志 + /// + /// 操作人Id + /// 操作人用户名 + /// 操作类型(使用 OperationLogActionType 常量) + /// 目标类型(使用 OperationLogTargetType 常量) + /// 目标记录Id + /// 目标名称 + /// 操作详情(可选,JSON格式) + /// IP地址(可选) + Task LogAsync(long operatorId, string operatorName, string actionType, string targetType, long targetId, string? targetName = null, string? detail = null, string? ipAddress = null); + + /// + /// 分页查询操作日志 + /// + /// 查询参数 + /// 分页结果 + Task> GetListAsync(OperationLogQueryInput input); + + /// + /// 获取操作日志详情 + /// + /// 日志Id + /// 日志详情 + Task GetDetailAsync(long id); +} diff --git a/QYZH.InteractiveMagazine.IService/IPetService.cs b/QYZH.InteractiveMagazine.IService/IPetService.cs index 15e801c..dda64f4 100644 --- a/QYZH.InteractiveMagazine.IService/IPetService.cs +++ b/QYZH.InteractiveMagazine.IService/IPetService.cs @@ -44,4 +44,112 @@ public interface IPetService : IBaseService /// 宠物Id /// 喂养记录列表 Task> GetFeedingRecordsAsync(long userId, long petId, PageQueryModel pageQuery); + + // ==================== 后台管理:宠物模板 ==================== + + /// + /// 创建宠物模板 + /// + Task CreateTemplateAsync(PetTemplateInput input); + + /// + /// 更新宠物模板 + /// + Task UpdateTemplateAsync(long id, PetTemplateInput input); + + /// + /// 删除宠物模板(软删除) + /// + Task DeleteTemplateAsync(long id); + + /// + /// 获取单个宠物模板 + /// + Task GetTemplateByIdAsync(long id); + + /// + /// 分页查询宠物模板列表 + /// + Task> GetTemplatesAsync(PetTemplateQueryInput input); + + /// + /// 更新模板状态(启用/禁用) + /// + Task UpdateTemplateStatusAsync(long id, string status); + + // ==================== 后台管理:进化链 ==================== + + /// + /// 创建进化阶段 + /// + Task CreateEvolutionAsync(PetEvolutionInput input); + + /// + /// 更新进化阶段 + /// + Task UpdateEvolutionAsync(long id, PetEvolutionInput input); + + /// + /// 删除进化阶段(软删除) + /// + Task DeleteEvolutionAsync(long id); + + /// + /// 获取单个进化阶段 + /// + Task GetEvolutionByIdAsync(long id); + + /// + /// 分页查询进化阶段列表 + /// + Task> GetEvolutionsAsync(PetEvolutionQueryInput input); + + // ==================== 后台管理:皮肤 ==================== + + /// + /// 创建皮肤 + /// + Task CreateSkinAsync(PetSkinInput input); + + /// + /// 更新皮肤 + /// + Task UpdateSkinAsync(long id, PetSkinInput input); + + /// + /// 删除皮肤(软删除) + /// + Task DeleteSkinAsync(long id); + + /// + /// 获取单个皮肤(含图片列表) + /// + Task GetSkinByIdAsync(long id); + + /// + /// 分页查询皮肤列表 + /// + Task> GetSkinsAsync(PetSkinQueryInput input); + + // ==================== 后台管理:皮肤图片 ==================== + + /// + /// 创建皮肤图片 + /// + Task CreateSkinImageAsync(PetSkinImageInput input); + + /// + /// 更新皮肤图片 + /// + Task UpdateSkinImageAsync(long id, PetSkinImageInput input); + + /// + /// 删除皮肤图片(软删除) + /// + Task DeleteSkinImageAsync(long id); + + /// + /// 获取指定皮肤的所有图片 + /// + Task> GetSkinImagesBySkinIdAsync(long skinId); } diff --git a/QYZH.InteractiveMagazine.IService/IPointsService.cs b/QYZH.InteractiveMagazine.IService/IPointsService.cs new file mode 100644 index 0000000..06ef2ff --- /dev/null +++ b/QYZH.InteractiveMagazine.IService/IPointsService.cs @@ -0,0 +1,66 @@ +using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Dto.Points; +using QYZH.InteractiveMagazine.Models.Entity; + +namespace QYZH.InteractiveMagazine.IService; + +/// +/// 积分服务接口 +/// +public interface IPointsService : IBaseService +{ + // ==================== 带事务版本(独立调用) ==================== + + /// + /// 增加积分(带事务) + /// + /// 增加积分参数 + /// 增加结果 + Task AddPointsAsync(AddPointsInput input); + + /// + /// 扣除积分(带事务,含余额不足校验) + /// + /// 扣除积分参数 + /// 扣除结果 + Task DeductPointsAsync(DeductPointsInput input); + + // ==================== 无事务版本(供外部事务调用) ==================== + + /// + /// 增加积分(无事务,需在外部事务中调用) + /// + /// 增加积分参数 + /// 增加结果 + Task AddPointsInTranAsync(AddPointsInput input); + + /// + /// 扣除积分(无事务,需在外部事务中调用,含余额不足校验) + /// + /// 扣除积分参数 + /// 扣除结果 + Task DeductPointsInTranAsync(DeductPointsInput input); + + // ==================== 查询 ==================== + + /// + /// 查询用户当前积分余额 + /// + /// 用户Id + /// 当前积分余额 + Task GetUserPointsAsync(long userId); + + /// + /// 获取用户积分概览(当前余额 + 累计收入 + 累计支出) + /// + /// 用户Id + /// 积分概览 + Task GetPointsSummaryAsync(long userId); + + /// + /// 分页查询积分流水 + /// + /// 查询参数 + /// 分页流水列表 + Task> GetPointsRecordsAsync(PointsRecordQueryInput input); +} diff --git a/QYZH.InteractiveMagazine.IService/IUsersService.cs b/QYZH.InteractiveMagazine.IService/IUsersService.cs index a38c67e..483587e 100644 --- a/QYZH.InteractiveMagazine.IService/IUsersService.cs +++ b/QYZH.InteractiveMagazine.IService/IUsersService.cs @@ -1,5 +1,5 @@ - using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Dto.Points; using QYZH.InteractiveMagazine.Models.Entity; namespace QYZH.InteractiveMagazine.IService; @@ -12,12 +12,22 @@ public interface IUsersService : IBaseService Task>> GetListAsync(UsersQueryInput input); /// - /// 获取用户详情 + /// 获取用户详情(包含积分记录、签到记录、补偿任务、期刊列表) /// - Task> GetDetailAsync(long id); + Task> GetDetailAsync(long id); /// /// 更新用户状态 /// Task UpdateStatusAsync(long id, UpdateUserStatusInput input); + + /// + /// 手动增加用户积分 + /// + Task ManualAddPointsAsync(long userId, ManualAddPointsInput input, long operatorId, string operatorName, string? ipAddress = null); + + /// + /// 手动扣除用户积分 + /// + Task ManualDeductPointsAsync(long userId, ManualDeductPointsInput input, long operatorId, string operatorName, string? ipAddress = null); } diff --git a/QYZH.InteractiveMagazine.Models/Dto/Admin/AdminUserDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Admin/AdminUserDto.cs index 762eb56..8186c97 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Admin/AdminUserDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Admin/AdminUserDto.cs @@ -1,4 +1,5 @@ using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Dto; @@ -20,7 +21,7 @@ public class AdminUserInput /// /// 管理员类型: SuperAdmin, Editor /// - public string Type { get; set; } = "Editor"; + public AdminUserTypeEnum Type { get; set; } = AdminUserTypeEnum.Editor; /// /// 状态: Active, Inactive @@ -92,5 +93,5 @@ public class AdminUserQueryInput : PageQueryModel /// /// 状态 /// - public string? Status { get; set; } + public int? Status { get; set; } } diff --git a/QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs index dda8f57..8ede844 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Community/CommunityDto.cs @@ -32,10 +32,6 @@ public class AdminMessageQueryInput : PageQueryModel /// public bool? IsActive { get; set; } - /// - /// 内容关键词(模糊查询) - /// - public string? Keyword { get; set; } /// /// 用户ID @@ -172,6 +168,17 @@ public class AdminFreezeInput public int Status { get; set; } } +/// +/// 批量发布消息入参 +/// +public class AdminBatchPublishInput +{ + /// + /// 消息ID列表 + /// + public List Ids { get; set; } = new(); +} + #endregion #region 微信端 DTO diff --git a/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationManageDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationManageDto.cs new file mode 100644 index 0000000..6c82ac7 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationManageDto.cs @@ -0,0 +1,241 @@ +using System.ComponentModel.DataAnnotations; +using QYZH.InteractiveMagazine.Models.Enum; + +namespace QYZH.InteractiveMagazine.Models.Dto.Compensation; + +/// +/// 补偿任务分页查询输入 +/// +public class CompensationTaskQueryInput : PageQueryModel +{ + /// + /// 按用户Id筛选 + /// + public long? UserId { get; set; } + + /// + /// 按任务类型筛选(PetFeeding/UserPoints/UserGrowth/SendNotification) + /// + public CompensationTaskTypeEnum? TaskType { get; set; } + + /// + /// 按状态筛选(Pending/Processing/Success/Failed/Cancelled) + /// + public CompensationTaskStatusEnum? Status { get; set; } + + /// + /// 按业务来源筛选(CheckIn/Purchase/Activity等) + /// + public string? BusinessSource { get; set; } +} + +/// +/// 手动重试补偿任务输入 +/// +public class CompensationRetryInput +{ + /// + /// 重试原因 + /// + [Required(ErrorMessage = "重试原因不能为空")] + public string Reason { get; set; } = string.Empty; +} + +/// +/// 标记补偿任务已解决输入 +/// +public class CompensationResolveInput +{ + /// + /// 处理说明 + /// + [Required(ErrorMessage = "处理说明不能为空")] + public string ResolveNote { get; set; } = string.Empty; +} + +/// +/// 补偿任务管理输出(含用户昵称) +/// +public class CompensationManageOutput +{ + /// + /// 任务Id + /// + public long Id { get; set; } + + /// + /// 任务类型 + /// + public CompensationTaskTypeEnum TaskType { get; set; } + + /// + /// 业务来源 + /// + public string BusinessSource { get; set; } = string.Empty; + + /// + /// 关联业务Id + /// + public string? BusinessId { get; set; } + + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 用户昵称 + /// + public string? UserName { get; set; } + + /// + /// 处理参数(JSON) + /// + public string Payload { get; set; } = string.Empty; + + /// + /// 异常消息 + /// + public string ErrorMessage { get; set; } = string.Empty; + + /// + /// 异常来源 + /// + public string ErrorSource { get; set; } = string.Empty; + + /// + /// 已重试次数 + /// + public int RetryCount { get; set; } + + /// + /// 最大重试次数 + /// + public int MaxRetries { get; set; } + + /// + /// 状态 + /// + public CompensationTaskStatusEnum Status { get; set; } + + /// + /// 最后处理时间 + /// + public DateTime? ProcessedAt { get; set; } + + /// + /// 计划执行时间 + /// + public DateTime? ScheduledAt { get; set; } + + /// + /// 处理结果 + /// + public string? ResultMessage { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } +} + +/// +/// 补偿任务详情输出(含审计字段) +/// +public class CompensationManageDetailOutput +{ + /// + /// 任务Id + /// + public long Id { get; set; } + + /// + /// 任务类型 + /// + public CompensationTaskTypeEnum TaskType { get; set; } + + /// + /// 业务来源 + /// + public string BusinessSource { get; set; } = string.Empty; + + /// + /// 关联业务Id + /// + public string? BusinessId { get; set; } + + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 用户昵称 + /// + public string? UserName { get; set; } + + /// + /// 处理参数(JSON) + /// + public string Payload { get; set; } = string.Empty; + + /// + /// 异常消息 + /// + public string ErrorMessage { get; set; } = string.Empty; + + /// + /// 异常来源 + /// + public string ErrorSource { get; set; } = string.Empty; + + /// + /// 已重试次数 + /// + public int RetryCount { get; set; } + + /// + /// 最大重试次数 + /// + public int MaxRetries { get; set; } + + /// + /// 状态 + /// + public CompensationTaskStatusEnum Status { get; set; } + + /// + /// 最后处理时间 + /// + public DateTime? ProcessedAt { get; set; } + + /// + /// 计划执行时间 + /// + public DateTime? ScheduledAt { get; set; } + + /// + /// 处理结果 + /// + public string? ResultMessage { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 创建人 + /// + public string? CreatedBy { get; set; } + + /// + /// 修改人 + /// + public string? UpdatedBy { get; set; } + + /// + /// 修改时间 + /// + public DateTime? UpdatedAt { get; set; } +} diff --git a/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationTaskDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationTaskDto.cs index c0e286c..6dd243c 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationTaskDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Compensation/CompensationTaskDto.cs @@ -1,56 +1,16 @@ +using QYZH.InteractiveMagazine.Models.Enum; + namespace QYZH.InteractiveMagazine.Models.Dto.Compensation; -/// -/// 补偿任务类型常量 -/// -public static class CompensationTaskType -{ - /// - /// 宠物喂养补偿 - /// Payload: { "PetId": long, "GrowthPoints": int } - /// - public const string PetFeeding = "PetFeeding"; - - /// - /// 用户积分补偿 - /// Payload: { "Points": int, "ChangeType": string, "Description": string } - /// - public const string UserPoints = "UserPoints"; - - /// - /// 用户成长值补偿 - /// Payload: { "GrowthPoints": int } - /// - public const string UserGrowth = "UserGrowth"; - - /// - /// 发送通知补偿 - /// Payload: { "TemplateId": string, "Data": object } - /// - public const string SendNotification = "SendNotification"; -} - -/// -/// 补偿任务状态常量 -/// -public static class CompensationTaskStatus -{ - public const string Pending = "Pending"; - public const string Processing = "Processing"; - public const string Success = "Success"; - public const string Failed = "Failed"; - public const string Cancelled = "Cancelled"; -} - /// /// 创建补偿任务输入 /// public class CreateCompensationTaskInput { /// - /// 任务类型(使用 CompensationTaskType 常量) + /// 任务类型(使用 CompensationTaskTypeEnum 枚举) /// - public string TaskType { get; set; } = string.Empty; + public CompensationTaskTypeEnum TaskType { get; set; } /// /// 业务来源(如 CheckIn、Purchase) @@ -94,7 +54,7 @@ public class CreateCompensationTaskInput public class CompensationTaskOutput { public long Id { get; set; } - public string TaskType { get; set; } = string.Empty; + public CompensationTaskTypeEnum TaskType { get; set; } public string BusinessSource { get; set; } = string.Empty; public string? BusinessId { get; set; } public long UserId { get; set; } @@ -103,7 +63,7 @@ public class CompensationTaskOutput public string ErrorSource { get; set; } = string.Empty; public int RetryCount { get; set; } public int MaxRetries { get; set; } - public string Status { get; set; } = string.Empty; + public CompensationTaskStatusEnum Status { get; set; } public DateTime? ProcessedAt { get; set; } public DateTime? ScheduledAt { get; set; } public string? ResultMessage { get; set; } @@ -118,12 +78,12 @@ public class GetCompensationTasksInput /// /// 按状态筛选 /// - public string? Status { get; set; } + public CompensationTaskStatusEnum? Status { get; set; } /// /// 按任务类型筛选 /// - public string? TaskType { get; set; } + public CompensationTaskTypeEnum? TaskType { get; set; } /// /// 按业务来源筛选 @@ -142,9 +102,9 @@ public class GetCompensationTasksInput public class UpdateCompensationStatusInput { /// - /// 目标状态(使用 CompensationTaskStatus 常量) + /// 目标状态(使用 CompensationTaskStatusEnum 枚举) /// - public string Status { get; set; } = string.Empty; + public CompensationTaskStatusEnum Status { get; set; } /// /// 处理结果描述 diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs index 2bf0000..12ef5ad 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs @@ -1,3 +1,5 @@ +using QYZH.InteractiveMagazine.Models.Enum; + namespace QYZH.InteractiveMagazine.Models.Dto; /// @@ -18,7 +20,7 @@ public class BindJournalInput /// /// 关联类型: Read(已读), Favorite(收藏), Subscribe(订阅),默认 Subscribe /// - public string Type { get; set; } = "Subscribe"; + public string Type { get; set; } = UserJournalTypeEnum.Subscribe.ToString(); } /// diff --git a/QYZH.InteractiveMagazine.Models/Dto/Mall/ProductDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Mall/ProductDto.cs index 3c4059f..d2c5588 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Mall/ProductDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Mall/ProductDto.cs @@ -1,4 +1,5 @@ using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Dto; diff --git a/QYZH.InteractiveMagazine.Models/Dto/Medal/MedalDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Medal/MedalDto.cs index 4f4e15e..ca1fd77 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Medal/MedalDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Medal/MedalDto.cs @@ -1,3 +1,5 @@ +using QYZH.InteractiveMagazine.Models.Enum; + namespace QYZH.InteractiveMagazine.Models.Dto; /// @@ -28,7 +30,7 @@ public class MedalInput /// /// 勋章的类型: Pet, Community /// - public string Type { get; set; } = "Pet"; + public string Type { get; set; } = MedalTypeEnum.Pet.ToString(); /// /// 书id diff --git a/QYZH.InteractiveMagazine.Models/Dto/OperationLogDto.cs b/QYZH.InteractiveMagazine.Models/Dto/OperationLogDto.cs new file mode 100644 index 0000000..c4c2bf6 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Dto/OperationLogDto.cs @@ -0,0 +1,201 @@ +namespace QYZH.InteractiveMagazine.Models.Dto; + +/// +/// 操作类型常量 +/// +public static class OperationLogActionType +{ + /// + /// 手动增加积分 + /// + public const string ManualAddPoints = "ManualAddPoints"; + + /// + /// 手动扣除积分 + /// + public const string ManualDeductPoints = "ManualDeductPoints"; + + /// + /// 补偿任务手动重试 + /// + public const string CompensationRetry = "CompensationRetry"; + + /// + /// 补偿任务标记已解决 + /// + public const string CompensationResolve = "CompensationResolve"; +} + +/// +/// 目标类型常量 +/// +public static class OperationLogTargetType +{ + /// + /// 用户 + /// + public const string User = "User"; + + /// + /// 补偿任务 + /// + public const string CompensationTask = "CompensationTask"; +} + +/// +/// 操作日志分页查询输入 +/// +public class OperationLogQueryInput : PageQueryModel +{ + /// + /// 按操作人用户名筛选(模糊查询) + /// + public string? OperatorName { get; set; } + + /// + /// 按操作类型筛选 + /// + public string? ActionType { get; set; } + + /// + /// 按目标类型筛选 + /// + public string? TargetType { get; set; } + + /// + /// 按目标记录Id筛选 + /// + public long? TargetId { get; set; } +} + +/// +/// 操作日志输出 +/// +public class OperationLogOutput +{ + /// + /// 记录Id + /// + public long Id { get; set; } + + /// + /// 操作人Id + /// + public long OperatorId { get; set; } + + /// + /// 操作人用户名 + /// + public string OperatorName { get; set; } = string.Empty; + + /// + /// 操作类型 + /// + public string ActionType { get; set; } = string.Empty; + + /// + /// 目标类型 + /// + public string TargetType { get; set; } = string.Empty; + + /// + /// 目标记录Id + /// + public long TargetId { get; set; } + + /// + /// 目标名称 + /// + public string? TargetName { get; set; } + + /// + /// 操作详情 + /// + public string? Detail { get; set; } + + /// + /// IP地址 + /// + public string? IpAddress { get; set; } + + /// + /// 操作时间 + /// + public DateTime CreatedAt { get; set; } +} + +/// +/// 操作日志详情输出 +/// +public class OperationLogDetailOutput +{ + /// + /// 记录Id + /// + public long Id { get; set; } + + /// + /// 操作类型 + /// + public string ActionType { get; set; } = string.Empty; + + /// + /// 目标类型 + /// + public string TargetType { get; set; } = string.Empty; + + /// + /// 目标记录Id + /// + public long TargetId { get; set; } + + /// + /// 目标名称 + /// + public string? TargetName { get; set; } + + /// + /// 操作详情 + /// + public string? Detail { get; set; } + + /// + /// IP地址 + /// + public string? IpAddress { get; set; } + + /// + /// 操作时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 操作人用户名 + /// + public string OperatorName { get; set; } = string.Empty; + + /// + /// 操作人手机号(管理员表无此字段时可为空) + /// + public string? OperatorPhone { get; set; } + + /// + /// 操作人角色/类型 + /// + public string? OperatorRole { get; set; } + + /// + /// 被操作人用户名(当 TargetType 为 User 时) + /// + public string? TargetUserName { get; set; } + + /// + /// 被操作人手机号(当 TargetType 为 User 时) + /// + public string? TargetUserPhone { get; set; } + + /// + /// 被操作人头像(当 TargetType 为 User 时) + /// + public string? TargetUserAvatar { get; set; } +} diff --git a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetEvolutionDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetEvolutionDto.cs new file mode 100644 index 0000000..d5816d5 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetEvolutionDto.cs @@ -0,0 +1,98 @@ +using QYZH.InteractiveMagazine.Models.Enum; + +namespace QYZH.InteractiveMagazine.Models.Dto.Pet; + +/// +/// 进化阶段创建/更新输入 +/// +public class PetEvolutionInput +{ + /// + /// 所属宠物模板Id + /// + public long TemplateId { get; set; } + + /// + /// 阶段名称 + /// + public string StageName { get; set; } = string.Empty; + + /// + /// 阶段等级 + /// + public int StageLevel { get; set; } + + /// + /// 进化所需成长值 + /// + public int RequiredGrowth { get; set; } + + /// + /// 前一形态Id(null表示初始形态) + /// + public long? PreviousEvolutionId { get; set; } + + /// + /// 基础力量 + /// + public int BaseStrength { get; set; } + + /// + /// 基础敏捷 + /// + public int BaseAgility { get; set; } + + /// + /// 基础智力 + /// + public int BaseIntelligence { get; set; } + + /// + /// 基础魅力 + /// + public int BaseCharm { get; set; } + + /// + /// 进化类型: Normal, Special + /// + public string Type { get; set; } = PetEvolutionTypeEnum.Normal.ToString(); +} + +/// +/// 进化阶段输出 +/// +public class PetEvolutionOutput +{ + public long Id { get; set; } + public long TemplateId { get; set; } + public string StageName { get; set; } = string.Empty; + public int StageLevel { get; set; } + public int RequiredGrowth { get; set; } + public long? PreviousEvolutionId { get; set; } + public int BaseStrength { get; set; } + public int BaseAgility { get; set; } + public int BaseIntelligence { get; set; } + public int BaseCharm { get; set; } + public string Type { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public string? CreatedBy { get; set; } + public DateTime CreatedAt { get; set; } + public string? UpdatedBy { get; set; } + public DateTime? UpdatedAt { get; set; } +} + +/// +/// 进化阶段分页查询输入 +/// +public class PetEvolutionQueryInput : PageQueryModel +{ + /// + /// 所属宠物模板Id(必填) + /// + public long TemplateId { get; set; } + + /// + /// 阶段名称(模糊搜索) + /// + public string? StageName { get; set; } +} diff --git a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetSkinDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetSkinDto.cs new file mode 100644 index 0000000..0b69af3 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetSkinDto.cs @@ -0,0 +1,131 @@ +using QYZH.InteractiveMagazine.Models.Enum; + +namespace QYZH.InteractiveMagazine.Models.Dto.Pet; + +/// +/// 皮肤创建/更新输入 +/// +public class PetSkinInput +{ + /// + /// 所属宠物模板Id + /// + public long TemplateId { get; set; } + + /// + /// 皮肤名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 描述/特效说明 + /// + public string? Description { get; set; } + + /// + /// 稀有度: Normal, Rare, Epic, Legendary + /// + public string Rarity { get; set; } = "Normal"; + + /// + /// 排序权重 + /// + public int SortOrder { get; set; } + + /// + /// 皮肤类型: Normal, Limited, Event + /// + public string Type { get; set; } = PetSkinTypeEnum.Normal.ToString(); +} + +/// +/// 皮肤输出 +/// +public class PetSkinOutput +{ + public long Id { get; set; } + public long TemplateId { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public string Rarity { get; set; } = string.Empty; + public int SortOrder { get; set; } + public string Type { get; set; } = string.Empty; + public string? CreatedBy { get; set; } + public DateTime CreatedAt { get; set; } + public string? UpdatedBy { get; set; } + public DateTime? UpdatedAt { get; set; } + + /// + /// 关联的皮肤图片列表 + /// + public List? Images { get; set; } +} + +/// +/// 皮肤分页查询输入 +/// +public class PetSkinQueryInput : PageQueryModel +{ + /// + /// 所属宠物模板Id(必填) + /// + public long TemplateId { get; set; } + + /// + /// 皮肤名称(模糊搜索) + /// + public string? Name { get; set; } + + /// + /// 稀有度: Normal, Rare, Epic, Legendary + /// + public string? Rarity { get; set; } +} + +/// +/// 皮肤图片创建/更新输入 +/// +public class PetSkinImageInput +{ + /// + /// 皮肤Id + /// + public long SkinId { get; set; } + + /// + /// 进化阶段Id + /// + public long EvolutionStageId { get; set; } + + /// + /// 图片地址 + /// + public string ImageUrl { get; set; } = string.Empty; + + /// + /// 图片顺序(动画帧序号) + /// + public int SortOrder { get; set; } + + /// + /// 图片类型: Normal, Special + /// + public string Type { get; set; } = PetSkinImageTypeEnum.Normal.ToString(); +} + +/// +/// 皮肤图片输出 +/// +public class PetSkinImageOutput +{ + public long Id { get; set; } + public long SkinId { get; set; } + public long EvolutionStageId { get; set; } + public string ImageUrl { get; set; } = string.Empty; + public int SortOrder { get; set; } + public string Type { get; set; } = string.Empty; + public string? CreatedBy { get; set; } + public DateTime CreatedAt { get; set; } + public string? UpdatedBy { get; set; } + public DateTime? UpdatedAt { get; set; } +} diff --git a/QYZH.InteractiveMagazine.Models/Dto/Pet/PetTemplateDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetTemplateDto.cs new file mode 100644 index 0000000..92d10fb --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Dto/Pet/PetTemplateDto.cs @@ -0,0 +1,79 @@ +using QYZH.InteractiveMagazine.Models.Enum; + +namespace QYZH.InteractiveMagazine.Models.Dto.Pet; + +/// +/// 宠物模板创建/更新输入 +/// +public class PetTemplateInput +{ + /// + /// 模板名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 模板描述 + /// + public string? Description { get; set; } + + /// + /// 默认初始进化形态Id + /// + public long DefaultEvolutionId { get; set; } + + /// + /// 模板图标地址 + /// + public string? IconUrl { get; set; } + + /// + /// 排序权重 + /// + public int SortOrder { get; set; } + + /// + /// 模板类型: Normal, Special, Limited + /// + public string Type { get; set; } = PetTemplateTypeEnum.Normal.ToString(); +} + +/// +/// 宠物模板输出 +/// +public class PetTemplateOutput +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public long DefaultEvolutionId { get; set; } + public string? IconUrl { get; set; } + public int SortOrder { get; set; } + public string Type { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public string? CreatedBy { get; set; } + public DateTime CreatedAt { get; set; } + public string? UpdatedBy { get; set; } + public DateTime? UpdatedAt { get; set; } +} + +/// +/// 宠物模板分页查询输入 +/// +public class PetTemplateQueryInput : PageQueryModel +{ + /// + /// 模板名称(模糊搜索) + /// + public string? Name { get; set; } + + /// + /// 模板类型: Normal, Special, Limited + /// + public string? Type { get; set; } + + /// + /// 状态: Active, Inactive + /// + public string? Status { get; set; } +} diff --git a/QYZH.InteractiveMagazine.Models/Dto/Points/PointsDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Points/PointsDto.cs new file mode 100644 index 0000000..2cd55a1 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Dto/Points/PointsDto.cs @@ -0,0 +1,219 @@ +namespace QYZH.InteractiveMagazine.Models.Dto.Points; +using QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 积分流水查询输入 +/// +public class PointsRecordQueryInput : PageQueryModel +{ + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 变动类型筛选 + /// + public PointsChangeTypeEnum? ChangeType { get; set; } + + /// + /// 流水分类筛选(Income/Expense) + /// + public PointsFlowTypeEnum? Type { get; set; } + + /// + /// 状态筛选(Success/Failed 等) + /// + public string? Status { get; set; } +} + +/// +/// 积分流水输出 +/// +public class PointsRecordOutput +{ + /// + /// 记录Id + /// + public long Id { get; set; } + + /// + /// 变动数值(正数为收入,负数为支出) + /// + public int ChangeAmount { get; set; } + + /// + /// 变动后余额 + /// + public int BalanceAfter { get; set; } + + /// + /// 变动类型(如 SignIn、Exchange、TaskReward 等) + /// + public string ChangeType { get; set; } = string.Empty; + + /// + /// 备注描述 + /// + public string? Description { get; set; } + + /// + /// 流水分类(Income/Expense) + /// + public string Type { get; set; } = string.Empty; + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } +} + +/// +/// 增加积分输入 +/// +public class AddPointsInput +{ + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 增加数量 + /// + public int Amount { get; set; } + + /// + /// 变动类型 + /// + public PointsChangeTypeEnum ChangeType { get; set; } + + /// + /// 关联业务Id + /// + public long? RelatedId { get; set; } + + /// + /// 备注描述 + /// + public string? Description { get; set; } + + /// + /// 操作人名称(为空时默认使用用户昵称) + /// + public string? OperatorName { get; set; } +} + +/// +/// 扣除积分输入 +/// +public class DeductPointsInput +{ + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 扣除数量 + /// + public int Amount { get; set; } + + /// + /// 变动类型 + /// + public PointsChangeTypeEnum ChangeType { get; set; } + + /// + /// 关联业务Id + /// + public long? RelatedId { get; set; } + + /// + /// 备注描述 + /// + public string? Description { get; set; } + + /// + /// 操作人名称(为空时默认使用用户昵称) + /// + public string? OperatorName { get; set; } +} + +/// +/// 增加积分输出 +/// +public class AddPointsOutput +{ + /// + /// 积分流水记录Id + /// + public long RecordId { get; set; } + + /// + /// 操作前积分余额 + /// + public int PreviousBalance { get; set; } + + /// + /// 操作后积分余额 + /// + public int NewBalance { get; set; } + + /// + /// 实际增加数量 + /// + public int AddedAmount { get; set; } +} + +/// +/// 扣除积分输出 +/// +public class DeductPointsOutput +{ + /// + /// 积分流水记录Id + /// + public long RecordId { get; set; } + + /// + /// 操作前积分余额 + /// + public int PreviousBalance { get; set; } + + /// + /// 操作后积分余额 + /// + public int NewBalance { get; set; } + + /// + /// 实际扣除数量 + /// + public int DeductedAmount { get; set; } +} + +/// +/// 用户积分概览 +/// +public class PointsSummaryOutput +{ + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 当前积分余额 + /// + public int CurrentBalance { get; set; } + + /// + /// 累计获得积分 + /// + public int TotalIncome { get; set; } + + /// + /// 累计消耗积分 + /// + public int TotalExpense { get; set; } +} diff --git a/QYZH.InteractiveMagazine.Models/Dto/Points/PointsManualDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Points/PointsManualDto.cs new file mode 100644 index 0000000..11f371a --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Dto/Points/PointsManualDto.cs @@ -0,0 +1,75 @@ +using System.ComponentModel.DataAnnotations; + +namespace QYZH.InteractiveMagazine.Models.Dto.Points; + +/// +/// 手动增加积分输入 +/// +public class ManualAddPointsInput +{ + /// + /// 增加数量(必须大于0) + /// + [Range(1, int.MaxValue, ErrorMessage = "增加积分数量必须大于0")] + public int Amount { get; set; } + + /// + /// 操作原因(必填) + /// + [Required(ErrorMessage = "操作原因不能为空")] + public string Reason { get; set; } = string.Empty; +} + +/// +/// 手动扣除积分输入 +/// +public class ManualDeductPointsInput +{ + /// + /// 扣除数量(必须大于0) + /// + [Range(1, int.MaxValue, ErrorMessage = "扣除积分数量必须大于0")] + public int Amount { get; set; } + + /// + /// 操作原因(必填) + /// + [Required(ErrorMessage = "操作原因不能为空")] + public string Reason { get; set; } = string.Empty; +} + +/// +/// 手动积分操作输出 +/// +public class ManualPointsOutput +{ + /// + /// 积分流水记录Id + /// + public long RecordId { get; set; } + + /// + /// 操作前积分余额 + /// + public int PreviousBalance { get; set; } + + /// + /// 操作后积分余额 + /// + public int NewBalance { get; set; } + + /// + /// 变动数量(正数为增加,负数为扣除) + /// + public int ChangeAmount { get; set; } + + /// + /// 操作人 + /// + public string OperatorName { get; set; } = string.Empty; + + /// + /// 操作时间 + /// + public DateTime OperatedAt { get; set; } +} diff --git a/QYZH.InteractiveMagazine.Models/Dto/UsersDto.cs b/QYZH.InteractiveMagazine.Models/Dto/UsersDto.cs index dd86b74..1af8426 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/UsersDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/UsersDto.cs @@ -1,4 +1,6 @@ -using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Dto.CheckIn; +using QYZH.InteractiveMagazine.Models.Dto.Compensation; +using QYZH.InteractiveMagazine.Models.Dto.Points; namespace QYZH.InteractiveMagazine.Models.Dto; @@ -59,6 +61,78 @@ public class UsersOutput public int GrowthPoints { get; set; } } +/// +/// 用户详情输出DTO(包含基本信息、积分记录、签到记录、补偿任务、期刊列表) +/// +public class UserDetailOutput +{ + /// + /// 用户基本信息 + /// + public UsersOutput BasicInfo { get; set; } = new(); + + /// + /// 积分使用记录(最近20条) + /// + public List PointsRecords { get; set; } = []; + + /// + /// 签到记录(最近30条) + /// + public List CheckInRecords { get; set; } = []; + + /// + /// 失败的补偿任务(需要手动处理) + /// + public List FailedCompensationTasks { get; set; } = []; + + /// + /// 用户拥有的期刊列表 + /// + public List Journals { get; set; } = []; +} + +/// +/// 用户期刊项输出DTO +/// +public class UserJournalItemOutput +{ + /// + /// 绑定记录Id + /// + public long BindId { get; set; } + + /// + /// 期刊Id + /// + public long JournalId { get; set; } + + /// + /// 期刊标题 + /// + public string JournalTitle { get; set; } = string.Empty; + + /// + /// 期刊封面 + /// + public string? CoverImageUrl { get; set; } + + /// + /// 绑定类型: Read, Favorite, Subscribe + /// + public string Type { get; set; } = string.Empty; + + /// + /// 绑定状态 + /// + public string Status { get; set; } = string.Empty; + + /// + /// 绑定时间 + /// + public DateTime CreatedAt { get; set; } +} + /// /// 更新用户状态输入DTO /// diff --git a/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs b/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs index 550251c..9ecc02f 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs @@ -1,4 +1,5 @@ using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Enum; using Newtonsoft.Json; namespace QYZH.InteractiveMagazine.Models.WeChat; @@ -89,12 +90,12 @@ public class WxUserInput /// /// 用户类型: Normal, VIP /// - public string Type { get; set; } = "Normal"; + public string Type { get; set; } = UsersTypeEnum.Normal.ToString(); /// /// 状态: Active, Disabled /// - public string Status { get; set; } = "Active"; + public string Status { get; set; } = UserStatusEnum.Active.ToString(); /// /// 密码,默认手机后4位 diff --git a/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs b/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs index 3218802..5cc1623 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -32,7 +33,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Editor /// Nullable:False /// - public string Type {get;set;} + public AdminUserTypeEnum Type {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs b/QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs index 46acea5..4374230 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -40,13 +41,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Daily /// Nullable:False /// - public string Type {get;set;} + public CheckInConfigTypeEnum Type {get;set;} /// /// Desc:状态: Active, Inactive /// Default:Active /// Nullable:False /// - public string Status {get;set;} + public CheckInConfigStatusEnum Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs b/QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs index 38b5e28..c48b5b6 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -52,13 +53,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type {get;set;} - - /// - /// Desc:状态: Success - /// Default:Success - /// Nullable:False - /// - public string Status {get;set;} + public CheckInRecordTypeEnum Type { get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs index df72a07..5795746 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -79,7 +80,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Article /// Nullable:False /// - public string Type { get; set; } + public MessageTypeEnum Type { get; set; } /// /// Desc:点赞数 diff --git a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageComment.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageComment.cs index ff87542..1590e66 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageComment.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageComment.cs @@ -1,11 +1,12 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { /// ///模板评论表 /// - [SugarTable("MessageComment")] + [SugarTable("CommunityMessageComment")] public partial class CommunityMessageComment : SqlSugarBaseEntity { public CommunityMessageComment(){ @@ -39,13 +40,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:TemplateComment /// Nullable:False /// - public string Type {get;set;} + public CommunityMessageCommentTypeEnum Type {get;set;} /// /// Desc:状态: Published, Hidden /// Default:Published /// Nullable:False /// - public string Status {get;set;} + public CommunityMessageCommentStatusEnum Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageLike.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageLike.cs index 7f29b78..8051847 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageLike.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageLike.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -32,7 +33,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Like /// Nullable:False /// - public string Type { get; set; } + public CommunityMessageLikeTypeEnum Type { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/CommunityTemplateSentence.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityTemplateSentence.cs index 1d8c8f8..f7ee681 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/CommunityTemplateSentence.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/CommunityTemplateSentence.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -33,13 +34,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Neutral /// Nullable:False /// - public string Type {get;set;} + public CommunityTemplateSentenceTypeEnum Type {get;set;} /// /// Desc:状态: Active, Inactive /// Default:Active /// Nullable:False /// - public string Status {get;set;} + public CommunityTemplateSentenceStatusEnum Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/CompensationTask.cs b/QYZH.InteractiveMagazine.Models/Entity/CompensationTask.cs index f549b24..08fd431 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/CompensationTask.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/CompensationTask.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -19,7 +20,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:False /// - public string TaskType { get; set; } + public int TaskType { get; set; } /// /// Desc:业务来源(如 CheckIn、Purchase、Activity) @@ -82,7 +83,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Pending /// Nullable:False /// - public new string Status { get; set; } + public CompensationTaskStatusEnum Status { get; set; } /// /// Desc:最后处理时间 diff --git a/QYZH.InteractiveMagazine.Models/Entity/DotFile.cs b/QYZH.InteractiveMagazine.Models/Entity/DotFile.cs index a92a498..9b51827 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/DotFile.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/DotFile.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text; using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -98,7 +99,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Pending /// Nullable:False /// - public string Status {get;set;} + public DotFileStatusEnum Status {get;set;} /// /// Desc: diff --git a/QYZH.InteractiveMagazine.Models/Entity/DotFileDetail.cs b/QYZH.InteractiveMagazine.Models/Entity/DotFileDetail.cs index a84ecdc..cd595c4 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/DotFileDetail.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/DotFileDetail.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text; using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -63,7 +64,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Pending /// Nullable:False /// - public string Status {get;set;} + public DotFileDetailStatusEnum Status {get;set;} /// /// Desc: diff --git a/QYZH.InteractiveMagazine.Models/Entity/ExchangeRecord.cs b/QYZH.InteractiveMagazine.Models/Entity/ExchangeRecord.cs index 928008c..dab7d7d 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/ExchangeRecord.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/ExchangeRecord.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -64,6 +65,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Success /// Nullable:False /// - public new string Status { get; set; } + public ExchangeRecordStatusEnum Status { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/Journal.cs b/QYZH.InteractiveMagazine.Models/Entity/Journal.cs index 48773a0..b977892 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/Journal.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/Journal.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -80,13 +81,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type {get;set;} + public JournalTypeEnum Type {get;set;} /// /// Desc:状态: Draft, Published, Archived /// Default:Draft /// Nullable:False /// - public string Status {get;set;} + public JournalStatusEnum Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/JournalCatalog.cs b/QYZH.InteractiveMagazine.Models/Entity/JournalCatalog.cs index 3d1d132..28154b2 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/JournalCatalog.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/JournalCatalog.cs @@ -1,7 +1,8 @@ -using System; +using QYZH.InteractiveMagazine.Models.Enum; +using SqlSugar; +using System; using System.Linq; using System.Text; -using SqlSugar; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -64,7 +65,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:0 /// Nullable:False /// - public int Type {get;set;} + public JournalCatalogTypeEnum Type {get;set;} } diff --git a/QYZH.InteractiveMagazine.Models/Entity/JournalPageTask.cs b/QYZH.InteractiveMagazine.Models/Entity/JournalPageTask.cs index 9da9535..0457435 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/JournalPageTask.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/JournalPageTask.cs @@ -1,7 +1,8 @@ -using System; +using QYZH.InteractiveMagazine.Models.Enum; +using SqlSugar; +using System; using System.Linq; using System.Text; -using SqlSugar; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -42,7 +43,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:False /// - public int Type {get;set;} + public JournalPageTaskTypeEnum Type {get;set;} /// /// Desc:任务 diff --git a/QYZH.InteractiveMagazine.Models/Entity/Medal.cs b/QYZH.InteractiveMagazine.Models/Entity/Medal.cs index a16c742..f8cea8b 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/Medal.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/Medal.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -47,7 +48,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Pet /// Nullable:False /// - public string Type { get; set; } + public MedalTypeEnum Type { get; set; } /// /// Desc:书id diff --git a/QYZH.InteractiveMagazine.Models/Entity/OperationLog.cs b/QYZH.InteractiveMagazine.Models/Entity/OperationLog.cs new file mode 100644 index 0000000..7634cfd --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/OperationLog.cs @@ -0,0 +1,68 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity; + +/// +/// 操作日志表 — 记录后台管理员的所有手动操作 +/// +[SugarTable("OperationLog")] +public partial class OperationLog : SqlSugarBaseEntity +{ + public OperationLog() { } + + /// + /// Desc:操作人Id(管理员Id) + /// Default: + /// Nullable:False + /// + public long OperatorId { get; set; } + + /// + /// Desc:操作人用户名 + /// Default: + /// Nullable:False + /// + public string OperatorName { get; set; } + + /// + /// Desc:操作类型(ManualAddPoints/ManualDeductPoints/CompensationRetry/CompensationResolve) + /// Default: + /// Nullable:False + /// + public string ActionType { get; set; } + + /// + /// Desc:目标类型(User/CompensationTask) + /// Default: + /// Nullable:False + /// + public string TargetType { get; set; } + + /// + /// Desc:目标记录Id + /// Default: + /// Nullable:False + /// + public long TargetId { get; set; } + + /// + /// Desc:目标名称(用户昵称/任务描述等,便于展示) + /// Default: + /// Nullable:True + /// + public string? TargetName { get; set; } + + /// + /// Desc:操作详情(JSON格式,记录操作参数和结果) + /// Default: + /// Nullable:True + /// + public string? Detail { get; set; } + + /// + /// Desc:操作IP地址 + /// Default: + /// Nullable:True + /// + public string? IpAddress { get; set; } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs b/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs index b243f7c..567e37e 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -78,13 +79,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type { get; set; } + public PetEvolutionTypeEnum Type { get; set; } /// /// Desc:状态: Active, Inactive /// Default:Active /// Nullable:False /// - public new string Status { get; set; } + public PetEvolutionStatusEnum Status { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs b/QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs index bd1891c..4346af7 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -60,13 +61,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type {get;set;} + public PetFeedingRecordTypeEnum Type {get;set;} /// /// Desc:状态 /// Default:Success /// Nullable:False /// - public string Status {get;set;} + public PetFeedingRecordStatusEnum Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs b/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs index 07c8f32..cdb98ed 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PetSkin.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -50,6 +51,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type { get; set; } + public PetSkinTypeEnum Type { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs b/QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs index e5d9714..91e519c 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PetSkinImage.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -43,6 +44,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type { get; set; } + public PetSkinImageTypeEnum Type { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetTemplate.cs b/QYZH.InteractiveMagazine.Models/Entity/PetTemplate.cs index 1453f4b..5077df2 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PetTemplate.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PetTemplate.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -50,13 +51,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type { get; set; } + public PetTemplateTypeEnum Type { get; set; } /// /// Desc:状态: Active, Inactive /// Default:Active /// Nullable:False /// - public new string Status { get; set; } + public PetTemplateStatusEnum Status { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs b/QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs index 8db8262..c0d2bee 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -60,13 +61,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:True /// - public string Type {get;set;} + public PointsFlowTypeEnum Type {get;set;} /// /// Desc:状态: Success, Failed, Pending /// Default:Success /// Nullable:False /// - public string Status {get;set;} + public PointsRecordStatusEnum Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/Product.cs b/QYZH.InteractiveMagazine.Models/Entity/Product.cs index aa882fe..4b8f912 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/Product.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/Product.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -46,7 +47,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:False /// - public string Type {get;set;} + public ProductTypeEnum Type {get;set;} /// /// Desc:售卖状态: OnSale, OffSale diff --git a/QYZH.InteractiveMagazine.Models/Entity/SqlSugarBaseEntity.cs b/QYZH.InteractiveMagazine.Models/Entity/SqlSugarBaseEntity.cs index b563260..b8c36b8 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/SqlSugarBaseEntity.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/SqlSugarBaseEntity.cs @@ -14,7 +14,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity [SugarColumn(IsPrimaryKey = true, ColumnName = "Id")] public long Id { get; set; } = YitIdHelper.NextId(); /// - /// Desc:状态 0禁用 1启用 + /// Desc: /// Default:1 /// Nullable:False /// diff --git a/QYZH.InteractiveMagazine.Models/Entity/UserBag.cs b/QYZH.InteractiveMagazine.Models/Entity/UserBag.cs index b1102b6..81fb11d 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/UserBag.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/UserBag.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -52,13 +53,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:True /// - public string Type {get;set;} + public UserBagTypeEnum Type {get;set;} /// /// Desc:状态: Available, Expired /// Default:Available /// Nullable:False /// - public string Status {get;set;} + public UserBagStatusEnum Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/UserJournal.cs b/QYZH.InteractiveMagazine.Models/Entity/UserJournal.cs index 138409a..edd4252 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/UserJournal.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/UserJournal.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -35,7 +36,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Nullable:False /// [SugarColumn(ColumnName = "Type")] - public string Type { get; set; } + public UserJournalTypeEnum Type { get; set; } /// /// Desc:状态: Active(正常), Inactive(失效) @@ -43,6 +44,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Nullable:False /// [SugarColumn(ColumnName = "Status")] - public new string Status { get; set; } + public UserJournalStatusEnum Status { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs b/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs index 9332d6b..2883914 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -38,13 +39,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:True /// - public string Type {get;set;} + public UserMedalTypeEnum Type {get;set;} /// /// Desc:状态: Awarded, Revoked /// Default:Awarded /// Nullable:False /// - public string Status {get;set;} + public UserMedalStatusEnum Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/UserPet.cs b/QYZH.InteractiveMagazine.Models/Entity/UserPet.cs index 4bf4d26..c5dc7ef 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/UserPet.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/UserPet.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -67,14 +68,14 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type { get; set; } + public UserPetTypeEnum Type { get; set; } /// /// Desc:状态: Inactive, Active, Sleeping /// Default:Inactive /// Nullable:False /// - public new string Status { get; set; } + public UserPetStatusEnum Status { get; set; } /// /// 理解力 diff --git a/QYZH.InteractiveMagazine.Models/Entity/Users.cs b/QYZH.InteractiveMagazine.Models/Entity/Users.cs index 8c1fc60..3a2af95 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/Users.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/Users.cs @@ -1,4 +1,5 @@ using SqlSugar; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.Models.Entity { @@ -39,14 +40,14 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default:Normal /// Nullable:False /// - public string Type { get; set; } + public UsersTypeEnum Type { get; set; } /// /// Desc:状态: Active, Disabled /// Default:Active /// Nullable:False /// - public string Status { get; set; } + public UserStatusEnum Status { get; set; } /// /// Desc:微信OpenId /// Default: diff --git a/QYZH.InteractiveMagazine.Models/Enum/AdminUserTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/AdminUserTypeEnum.cs new file mode 100644 index 0000000..dcfca63 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/AdminUserTypeEnum.cs @@ -0,0 +1,20 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 管理员类型枚举 +/// +public enum AdminUserTypeEnum +{ + /// + /// 编辑员 + /// + Editor = 2, + /// + /// 超级管理员 + /// + SuperAdmin = 0, + /// + /// 超级管理员 + /// + Admin = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CheckInConfigStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CheckInConfigStatusEnum.cs new file mode 100644 index 0000000..3a46bb1 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CheckInConfigStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 签到配置状态枚举 +/// +public enum CheckInConfigStatusEnum +{ + /// + /// 未激活 + /// + Inactive = 0, + + /// + /// 激活 + /// + Active = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CheckInConfigTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CheckInConfigTypeEnum.cs new file mode 100644 index 0000000..18f1290 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CheckInConfigTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 签到配置类型枚举 +/// +public enum CheckInConfigTypeEnum +{ + /// + /// 每日签到 + /// + Daily = 1, + /// + /// 连续签到 + /// + Streak = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CheckInRecordStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CheckInRecordStatusEnum.cs new file mode 100644 index 0000000..dd61915 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CheckInRecordStatusEnum.cs @@ -0,0 +1,12 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 签到记录状态枚举 +/// +public enum CheckInRecordStatusEnum +{ + /// + /// 成功 + /// + Success = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CheckInRecordTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CheckInRecordTypeEnum.cs new file mode 100644 index 0000000..606be79 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CheckInRecordTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 签到记录类型枚举 +/// +public enum CheckInRecordTypeEnum +{ + /// + /// 正常签到 + /// + Normal = 1, + /// + /// 补签 + /// + MakeUp = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageCommentStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageCommentStatusEnum.cs new file mode 100644 index 0000000..36d839c --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageCommentStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 模板评论状态枚举 +/// +public enum CommunityMessageCommentStatusEnum +{ + /// + /// 已隐藏 + /// + Hidden = 0, + + /// + /// 已发布 + /// + Published = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageCommentTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageCommentTypeEnum.cs new file mode 100644 index 0000000..e6d9ea4 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageCommentTypeEnum.cs @@ -0,0 +1,12 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 评论类型枚举 +/// +public enum CommunityMessageCommentTypeEnum +{ + /// + /// 模板评论 + /// + TemplateComment = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageLikeTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageLikeTypeEnum.cs new file mode 100644 index 0000000..6105097 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CommunityMessageLikeTypeEnum.cs @@ -0,0 +1,12 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 点赞类型枚举 +/// +public enum CommunityMessageLikeTypeEnum +{ + /// + /// 点赞 + /// + Like = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CommunityTemplateSentenceStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CommunityTemplateSentenceStatusEnum.cs new file mode 100644 index 0000000..c7e0773 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CommunityTemplateSentenceStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 固定模板言论状态枚举 +/// +public enum CommunityTemplateSentenceStatusEnum +{ + /// + /// 未激活 + /// + Inactive = 0, + + /// + /// 激活 + /// + Active = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CommunityTemplateSentenceTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CommunityTemplateSentenceTypeEnum.cs new file mode 100644 index 0000000..9687425 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CommunityTemplateSentenceTypeEnum.cs @@ -0,0 +1,20 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 固定模板言论分类枚举 +/// +public enum CommunityTemplateSentenceTypeEnum +{ + /// + /// 正面 + /// + Positive = 1, + /// + /// 中性 + /// + Neutral = 2, + /// + /// 负面 + /// + Negative = 3 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CompensationTaskStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CompensationTaskStatusEnum.cs new file mode 100644 index 0000000..db313f7 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CompensationTaskStatusEnum.cs @@ -0,0 +1,32 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 补偿任务状态枚举 +/// +public enum CompensationTaskStatusEnum +{ + /// + /// 待处理 + /// + Pending = 0, + + /// + /// 处理中 + /// + Processing = 1, + + /// + /// 成功 + /// + Success = 2, + + /// + /// 失败 + /// + Failed = 3, + + /// + /// 已取消 + /// + Cancelled = 4 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/CompensationTaskTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/CompensationTaskTypeEnum.cs new file mode 100644 index 0000000..1417673 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/CompensationTaskTypeEnum.cs @@ -0,0 +1,24 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 补偿任务类型枚举 +/// +public enum CompensationTaskTypeEnum +{ + /// + /// 宠物喂养补偿 + /// + PetFeeding = 1, + /// + /// 用户积分补偿 + /// + UserPoints = 2, + /// + /// 用户成长值补偿 + /// + UserGrowth = 3, + /// + /// 发送通知补偿 + /// + SendNotification = 4 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/DotFileDetailStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/DotFileDetailStatusEnum.cs new file mode 100644 index 0000000..d0ba8f7 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/DotFileDetailStatusEnum.cs @@ -0,0 +1,32 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 点读文件明细状态枚举 +/// +public enum DotFileDetailStatusEnum +{ + /// + /// 待处理 + /// + Pending = 0, + + /// + /// 处理中 + /// + Processing = 1, + + /// + /// 成功 + /// + Success = 2, + + /// + /// 失败 + /// + Failed = 3, + + /// + /// 已取消 + /// + Cancelled = 4 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/DotFileStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/DotFileStatusEnum.cs new file mode 100644 index 0000000..c6099c7 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/DotFileStatusEnum.cs @@ -0,0 +1,32 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 点读文件状态枚举 +/// +public enum DotFileStatusEnum +{ + /// + /// 待处理 + /// + Pending = 0, + + /// + /// 处理中 + /// + Processing = 1, + + /// + /// 成功 + /// + Success = 2, + + /// + /// 失败 + /// + Failed = 3, + + /// + /// 已取消 + /// + Cancelled = 4 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/ExchangeRecordStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/ExchangeRecordStatusEnum.cs new file mode 100644 index 0000000..827ba8c --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/ExchangeRecordStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 兑换记录状态枚举 +/// +public enum ExchangeRecordStatusEnum +{ + /// + /// 成功 + /// + Success = 1, + + /// + /// 已退还 + /// + Refunded = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/JournalCatalogTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/JournalCatalogTypeEnum.cs new file mode 100644 index 0000000..b827b56 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/JournalCatalogTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 期刊目录类别枚举 +/// +public enum JournalCatalogTypeEnum +{ + /// + /// 单元 + /// + Unit = 0, + /// + /// 页面 + /// + Page = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/JournalPageStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/JournalPageStatusEnum.cs new file mode 100644 index 0000000..66726f1 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/JournalPageStatusEnum.cs @@ -0,0 +1,22 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 期刊页面状态枚举 +/// +public enum JournalPageStatusEnum +{ + /// + /// 验证失败 + /// + Failed = -1, + + /// + /// 未验证 + /// + NotVerified = 0, + + /// + /// 验证成功 + /// + Success = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/JournalPageTaskTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/JournalPageTaskTypeEnum.cs new file mode 100644 index 0000000..18b9a60 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/JournalPageTaskTypeEnum.cs @@ -0,0 +1,24 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 杂志任务题型枚举 +/// +public enum JournalPageTaskTypeEnum +{ + /// + /// 单选题 + /// + SingleChoice = 1, + /// + /// 多选题 + /// + MultipleChoice = 2, + /// + /// 填空题 + /// + FillInBlank = 3, + /// + /// 问答题 + /// + QuestionAnswer = 4 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/JournalStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/JournalStatusEnum.cs new file mode 100644 index 0000000..7fd1009 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/JournalStatusEnum.cs @@ -0,0 +1,22 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 期刊状态枚举 +/// +public enum JournalStatusEnum +{ + /// + /// 草稿 + /// + Draft = 0, + + /// + /// 已发布 + /// + Published = 1, + + /// + /// 已归档 + /// + Archived = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/JournalTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/JournalTypeEnum.cs new file mode 100644 index 0000000..a47e011 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/JournalTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 期刊类型枚举 +/// +public enum JournalTypeEnum +{ + /// + /// 普通 + /// + Normal = 1, + /// + /// 特刊 + /// + Special = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/MedalTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/MedalTypeEnum.cs new file mode 100644 index 0000000..aaea1a6 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/MedalTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 勋章类型枚举 +/// +public enum MedalTypeEnum +{ + /// + /// 宠物勋章 + /// + Pet = 1, + /// + /// 社区勋章 + /// + Community = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/MessageStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/MessageStatusEnum.cs new file mode 100644 index 0000000..f548838 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/MessageStatusEnum.cs @@ -0,0 +1,22 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 社区消息状态枚举 +/// +public enum MessageStatusEnum +{ + /// + /// 审核中 + /// + PendingReview = 0, + + /// + /// 已通过 + /// + Approved = 1, + + /// + /// 已冻结 + /// + Frozen = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/MessageTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/MessageTypeEnum.cs new file mode 100644 index 0000000..ccf38c6 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/MessageTypeEnum.cs @@ -0,0 +1,22 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 消息类型 +/// +public enum MessageTypeEnum +{ + /// + /// 文章 + /// + Article = 1, + + /// + /// 引用 + /// + Quote = 2, + + /// + /// 公告 + /// + Announcement = 3 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetEvolutionStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetEvolutionStatusEnum.cs new file mode 100644 index 0000000..0e68631 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PetEvolutionStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 宠物进化状态枚举 +/// +public enum PetEvolutionStatusEnum +{ + /// + /// 未激活 + /// + Inactive = 0, + + /// + /// 激活 + /// + Active = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetEvolutionTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetEvolutionTypeEnum.cs new file mode 100644 index 0000000..b90b46f --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PetEvolutionTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 宠物进化类型枚举 +/// +public enum PetEvolutionTypeEnum +{ + /// + /// 普通进化 + /// + Normal = 1, + /// + /// 特殊进化 + /// + Special = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetFeedingRecordStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetFeedingRecordStatusEnum.cs new file mode 100644 index 0000000..85a3976 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PetFeedingRecordStatusEnum.cs @@ -0,0 +1,12 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 宠物喂养记录状态枚举 +/// +public enum PetFeedingRecordStatusEnum +{ + /// + /// 成功 + /// + Success = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetFeedingRecordTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetFeedingRecordTypeEnum.cs new file mode 100644 index 0000000..4a48254 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PetFeedingRecordTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 宠物喂养记录类型枚举 +/// +public enum PetFeedingRecordTypeEnum +{ + /// + /// 普通喂养 + /// + Normal = 1, + /// + /// 特殊喂养 + /// + Special = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetSkinImageTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetSkinImageTypeEnum.cs new file mode 100644 index 0000000..0600890 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PetSkinImageTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 宠物皮肤图片类型枚举 +/// +public enum PetSkinImageTypeEnum +{ + /// + /// 普通图片 + /// + Normal = 1, + /// + /// 特殊图片 + /// + Special = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetSkinTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetSkinTypeEnum.cs new file mode 100644 index 0000000..3fbb886 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PetSkinTypeEnum.cs @@ -0,0 +1,20 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 宠物皮肤类型枚举 +/// +public enum PetSkinTypeEnum +{ + /// + /// 普通皮肤 + /// + Normal = 1, + /// + /// 限定皮肤 + /// + Limited = 2, + /// + /// 活动皮肤 + /// + Event = 3 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetTemplateStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetTemplateStatusEnum.cs new file mode 100644 index 0000000..651e5e4 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PetTemplateStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 宠物模板状态枚举 +/// +public enum PetTemplateStatusEnum +{ + /// + /// 未激活 + /// + Inactive = 0, + + /// + /// 激活 + /// + Active = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PetTemplateTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PetTemplateTypeEnum.cs new file mode 100644 index 0000000..cb6b65b --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PetTemplateTypeEnum.cs @@ -0,0 +1,20 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 宠物模板类型枚举 +/// +public enum PetTemplateTypeEnum +{ + /// + /// 普通 + /// + Normal = 1, + /// + /// 特殊 + /// + Special = 2, + /// + /// 限定 + /// + Limited = 3 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PointsChangeTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PointsChangeTypeEnum.cs new file mode 100644 index 0000000..6d41020 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PointsChangeTypeEnum.cs @@ -0,0 +1,52 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 积分变动类型 +/// +public enum PointsChangeTypeEnum +{ + /// + /// 签到奖励 + /// + SignIn, + + /// + /// 补签奖励 + /// + MakeUpSign, + + /// + /// 商城兑换扣除 + /// + Exchange, + + /// + /// 宠物喂养消耗 + /// + FeedPet, + + /// + /// 任务奖励(杂志任务等) + /// + TaskReward, + + /// + /// 注册奖励 + /// + RegisterBonus, + + /// + /// 勋章奖励 + /// + MedalBonus, + + /// + /// 系统补偿 + /// + SystemCompensation, + + /// + /// 后台手动调整 + /// + ManualAdjust +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PointsFlowTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PointsFlowTypeEnum.cs new file mode 100644 index 0000000..3d4e2c2 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PointsFlowTypeEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 积分流水分类 +/// +public enum PointsFlowTypeEnum +{ + /// + /// 收入 + /// + Income, + + /// + /// 支出 + /// + Expense +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/PointsRecordStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/PointsRecordStatusEnum.cs new file mode 100644 index 0000000..c58550f --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/PointsRecordStatusEnum.cs @@ -0,0 +1,22 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 积分流水记录状态枚举 +/// +public enum PointsRecordStatusEnum +{ + /// + /// 待处理 + /// + Pending = 0, + + /// + /// 成功 + /// + Success = 1, + + /// + /// 失败 + /// + Failed = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/ProductTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/ProductTypeEnum.cs new file mode 100644 index 0000000..50e530e --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/ProductTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 商品类型枚举 +/// +public enum ProductTypeEnum +{ + /// + /// 补签卡 + /// + MakeUpCard = 1, + /// + /// 宠物背景 + /// + PetBg = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserBagStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserBagStatusEnum.cs new file mode 100644 index 0000000..44e74a3 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UserBagStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户背包状态枚举 +/// +public enum UserBagStatusEnum +{ + /// + /// 已过期 + /// + Expired = 0, + + /// + /// 可用 + /// + Available = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserBagTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserBagTypeEnum.cs new file mode 100644 index 0000000..dcd2e47 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UserBagTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户背包物品分类枚举 +/// +public enum UserBagTypeEnum +{ + /// + /// 补签卡 + /// + MakeUpCard = 1, + /// + /// 宠物背景 + /// + PetBg = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserJournalStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserJournalStatusEnum.cs new file mode 100644 index 0000000..cf29326 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UserJournalStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户期刊关联状态枚举 +/// +public enum UserJournalStatusEnum +{ + /// + /// 失效 + /// + Inactive = 0, + + /// + /// 正常 + /// + Active = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserJournalTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserJournalTypeEnum.cs new file mode 100644 index 0000000..d3bd261 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UserJournalTypeEnum.cs @@ -0,0 +1,20 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户期刊关联类型枚举 +/// +public enum UserJournalTypeEnum +{ + /// + /// 已读 + /// + Read = 1, + /// + /// 收藏 + /// + Favorite = 2, + /// + /// 订阅 + /// + Subscribe = 3 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserMedalStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserMedalStatusEnum.cs new file mode 100644 index 0000000..9332e4a --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UserMedalStatusEnum.cs @@ -0,0 +1,17 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户勋章状态枚举 +/// +public enum UserMedalStatusEnum +{ + /// + /// 已撤销 + /// + Revoked = 0, + + /// + /// 已授予 + /// + Awarded = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserMedalTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserMedalTypeEnum.cs new file mode 100644 index 0000000..c8ebfa0 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UserMedalTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户勋章记录类型枚举 +/// +public enum UserMedalTypeEnum +{ + /// + /// 系统授予 + /// + System = 1, + /// + /// 活动奖励 + /// + Activity = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserPetStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserPetStatusEnum.cs new file mode 100644 index 0000000..cfa4b9e --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UserPetStatusEnum.cs @@ -0,0 +1,22 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户宠物状态枚举 +/// +public enum UserPetStatusEnum +{ + /// + /// 未激活 + /// + Inactive = 0, + + /// + /// 激活 + /// + Active = 1, + + /// + /// 休眠中 + /// + Sleeping = 2 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserPetTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserPetTypeEnum.cs new file mode 100644 index 0000000..a38d14d --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UserPetTypeEnum.cs @@ -0,0 +1,12 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户宠物类型枚举 +/// +public enum UserPetTypeEnum +{ + /// + /// 普通 + /// + Normal = 1 +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/UserStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UserStatusEnum.cs index f215f63..5a0014c 100644 --- a/QYZH.InteractiveMagazine.Models/Enum/UserStatusEnum.cs +++ b/QYZH.InteractiveMagazine.Models/Enum/UserStatusEnum.cs @@ -11,7 +11,7 @@ public enum UserStatusEnum Active = 1, /// - /// 冻结 + /// 禁用 /// - Frozen = 2 + Disabled = 2 } diff --git a/QYZH.InteractiveMagazine.Models/Enum/UsersTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/UsersTypeEnum.cs new file mode 100644 index 0000000..1491604 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/UsersTypeEnum.cs @@ -0,0 +1,16 @@ +namespace QYZH.InteractiveMagazine.Models.Enum; + +/// +/// 用户类型枚举 +/// +public enum UsersTypeEnum +{ + /// + /// 普通用户 + /// + Normal = 1, + /// + /// VIP用户 + /// + VIP = 2 +} diff --git a/QYZH.InteractiveMagazine.Service/AdminAuthService.cs b/QYZH.InteractiveMagazine.Service/AdminAuthService.cs index dcefeab..0087406 100644 --- a/QYZH.InteractiveMagazine.Service/AdminAuthService.cs +++ b/QYZH.InteractiveMagazine.Service/AdminAuthService.cs @@ -66,7 +66,7 @@ public class AdminAuthService(BaseRepository adminUserRepository, ICo Token = token, UserId = (long)adminUser.Id, UserName = adminUser.UserName, - Type = adminUser.Type + Type = adminUser.Type.ToString(), }; } @@ -94,7 +94,7 @@ public class AdminAuthService(BaseRepository adminUserRepository, ICo { UserId = adminUser.Id, UserName = adminUser.UserName, - Type = adminUser.Type, + Type = adminUser.Type.ToString(), Status = adminUser.Status }; } diff --git a/QYZH.InteractiveMagazine.Service/AdminUserService.cs b/QYZH.InteractiveMagazine.Service/AdminUserService.cs index ea5b11b..71a260f 100644 --- a/QYZH.InteractiveMagazine.Service/AdminUserService.cs +++ b/QYZH.InteractiveMagazine.Service/AdminUserService.cs @@ -6,6 +6,7 @@ using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using SqlSugar; @@ -64,7 +65,7 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo logger.LogInformation("管理员创建成功,用户名: {UserName}, ID: {Id}", input.UserName, adminUser.Id); - return new AdminUserOutput { Id = adminUser.Id, UserName = adminUser.UserName, Type = adminUser.Type, Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, UpdatedBy = adminUser.UpdatedBy, UpdatedAt = adminUser.UpdatedAt }; + return new AdminUserOutput { Id = adminUser.Id, UserName = adminUser.UserName, Type = adminUser.Type.ToString(), Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, UpdatedBy = adminUser.UpdatedBy, UpdatedAt = adminUser.UpdatedAt }; } /// @@ -100,10 +101,7 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo adminUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(input.Password); } - if (!string.IsNullOrWhiteSpace(input.Type)) - { - adminUser.Type = input.Type; - } + adminUser.Type = input.Type; adminUser.UpdatedBy = "System"; @@ -118,7 +116,7 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo logger.LogInformation("管理员更新成功,ID: {Id}", id); - return new AdminUserOutput { Id = adminUser.Id, UserName = adminUser.UserName, Type = adminUser.Type, Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, UpdatedBy = adminUser.UpdatedBy, UpdatedAt = adminUser.UpdatedAt }; + return new AdminUserOutput { Id = adminUser.Id, UserName = adminUser.UserName, Type = adminUser.Type.ToString(), Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, UpdatedBy = adminUser.UpdatedBy, UpdatedAt = adminUser.UpdatedAt }; } /// @@ -162,7 +160,7 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo { Id = adminUser.Id, UserName = adminUser.UserName, - Type = adminUser.Type, + Type = adminUser.Type.ToString(), Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, @@ -195,7 +193,7 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo { Id = a.Id, UserName = a.UserName, - Type = a.Type, + Type = a.Type.ToString(), Status = a.Status, CreatedBy = a.CreatedBy, CreatedAt = a.CreatedAt, diff --git a/QYZH.InteractiveMagazine.Service/CheckInService.cs b/QYZH.InteractiveMagazine.Service/CheckInService.cs index a084782..dcea9ea 100644 --- a/QYZH.InteractiveMagazine.Service/CheckInService.cs +++ b/QYZH.InteractiveMagazine.Service/CheckInService.cs @@ -1,10 +1,13 @@ using Microsoft.Extensions.Logging; +using QYZH.InteractiveMagazine.Common.Extensions; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto.CheckIn; using QYZH.InteractiveMagazine.Models.Dto.Compensation; using QYZH.InteractiveMagazine.Models.Dto.Pet; +using QYZH.InteractiveMagazine.Models.Dto.Points; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; namespace QYZH.InteractiveMagazine.Service; @@ -16,6 +19,7 @@ public class CheckInService( BaseRepository checkInRecordRepository, IPetService petService, ICompensationTaskService compensationTaskService, + IPointsService pointsService, ILogger logger) : BaseRepository, ICheckInService { @@ -77,35 +81,29 @@ public class CheckInService( PointsAwarded = pointsReward, GrowthPointsAwarded = growthReward, ConsecutiveDays = consecutiveDays, - Type = "Normal", - Status = "Success" + Type = CheckInRecordTypeEnum.Normal, + Status = (int)CheckInRecordStatusEnum.Success }; var recordId = await checkInRecordRepository.Insertable(checkInRecord).ExecuteReturnIdentityAsync(); checkInRecord.Id = recordId; - // 6b. 更新用户积分余额 - var newPointsBalance = user.Points + pointsReward; + // 6b. 更新用户成长值 var newGrowthBalance = user.GrowthPoints + growthReward; await checkInRecordRepository.Context.Updateable() - .SetColumns(u => u.Points == newPointsBalance) .SetColumns(u => u.GrowthPoints == newGrowthBalance) .Where(u => u.Id == userId && !u.IsDeleted) .ExecuteCommandAsync(); - // 6c. 创建积分变动记录 - var pointsRecord = new PointsRecord + // 6c. 通过积分服务增加积分 + var pointsResult = await pointsService.AddPointsInTranAsync(new AddPointsInput { UserId = userId, - ChangeAmount = pointsReward, - BalanceAfter = newPointsBalance, - ChangeType = "SignIn", + Amount = pointsReward, + ChangeType = PointsChangeTypeEnum.SignIn, RelatedId = recordId, - Description = $"签到奖励(连续{consecutiveDays}天)", - Type = "Income", - Status = "Success" - }; - await checkInRecordRepository.Context.Insertable(pointsRecord).ExecuteCommandAsync(); + Description = $"签到奖励(连续{consecutiveDays}天)" + }); // 构建返回结果 result.RecordId = (long)recordId; @@ -113,13 +111,13 @@ public class CheckInService( result.ConsecutiveDays = consecutiveDays; result.PointsAwarded = pointsReward; result.GrowthPointsAwarded = growthReward; - result.PointsBalance = newPointsBalance; + result.PointsBalance = pointsResult.NewBalance; result.GrowthPointsBalance = newGrowthBalance; result.HasPet = pet != null; }); // 7. 如果用户有活跃宠物,调用 PetService 喂养(含进化检查),独立事务 - if (pet != null && pet.Status == "Active" && growthReward > 0) + if (pet != null && pet.Status == UserPetStatusEnum.Active && growthReward > 0) { try { @@ -141,7 +139,7 @@ public class CheckInService( await compensationTaskService.CreateTaskAsync(new CreateCompensationTaskInput { - TaskType = CompensationTaskType.PetFeeding, + TaskType = CompensationTaskTypeEnum.PetFeeding, BusinessSource = "CheckIn", BusinessId = result.RecordId.ToString(), UserId = userId, @@ -212,8 +210,8 @@ public class CheckInService( ConsecutiveDays = r.ConsecutiveDays, PointsAwarded = r.PointsAwarded, GrowthPointsAwarded = r.GrowthPointsAwarded, - Type = r.Type, - Status = r.Status + Type = r.Type.ToString(), + Status = r.Status.ToString() }) .ToListAsync(); @@ -277,8 +275,8 @@ public class CheckInService( PointsAwarded = pointsReward, GrowthPointsAwarded = growthReward, ConsecutiveDays = 0, // 补签不纳入连续天数 - Type = "MakeUp", - Status = "Success", + Type = CheckInRecordTypeEnum.MakeUp, + Status = (int)CheckInRecordStatusEnum.Success, IsDeleted = false, CreatedBy = userId.ToString(), CreatedAt = DateTime.Now, @@ -288,47 +286,36 @@ public class CheckInService( var recordId = await checkInRecordRepository.Insertable(checkInRecord).ExecuteReturnIdentityAsync(); checkInRecord.Id = recordId; - // 更新用户积分和成长值 - var newPointsBalance = user.Points + pointsReward; + // 更新用户成长值 var newGrowthBalance = user.GrowthPoints + growthReward; await checkInRecordRepository.Context.Updateable() - .SetColumns(u => u.Points == newPointsBalance) .SetColumns(u => u.GrowthPoints == newGrowthBalance) .Where(u => u.Id == userId && !u.IsDeleted) .ExecuteCommandAsync(); - // 创建积分变动记录 - var pointsRecord = new PointsRecord + // 通过积分服务增加积分 + var pointsResult = await pointsService.AddPointsInTranAsync(new AddPointsInput { UserId = userId, - ChangeAmount = pointsReward, - BalanceAfter = newPointsBalance, - ChangeType = "MakeUpSign", + Amount = pointsReward, + ChangeType = PointsChangeTypeEnum.MakeUpSign, RelatedId = recordId, - Description = $"补签奖励({targetDate:yyyy-MM-dd})", - Type = "Income", - Status = "Success", - IsDeleted = false, - CreatedBy = userId.ToString(), - CreatedAt = DateTime.Now, - UpdatedBy = userId.ToString(), - UpdatedAt = DateTime.Now - }; - await checkInRecordRepository.Context.Insertable(pointsRecord).ExecuteCommandAsync(); + Description = $"补签奖励({targetDate:yyyy-MM-dd})" + }); result.RecordId = (long)recordId; result.CheckInDate = targetDate; result.ConsecutiveDays = 0; result.PointsAwarded = pointsReward; result.GrowthPointsAwarded = growthReward; - result.PointsBalance = newPointsBalance; + result.PointsBalance = pointsResult.NewBalance; result.GrowthPointsBalance = newGrowthBalance; result.HasPet = pet != null; }); // 如果有活跃宠物,喂养成长值 - if (pet != null && pet.Status == "Active" && growthReward > 0) + if (pet != null && pet.Status == UserPetStatusEnum.Active && growthReward > 0) { try { @@ -404,7 +391,7 @@ public class CheckInService( { // 查询签到配置(按 DayNumber 升序) var configs = await checkInRecordRepository.Context.Queryable() - .Where(c => c.Status == "Active" && !c.IsDeleted) + .Where(c => c.Status == CheckInConfigStatusEnum.Active && !c.IsDeleted) .OrderBy(c => c.DayNumber) .ToListAsync(); diff --git a/QYZH.InteractiveMagazine.Service/CommunityMessageService.cs b/QYZH.InteractiveMagazine.Service/CommunityMessageService.cs index 591eb97..6b711a8 100644 --- a/QYZH.InteractiveMagazine.Service/CommunityMessageService.cs +++ b/QYZH.InteractiveMagazine.Service/CommunityMessageService.cs @@ -1,8 +1,10 @@ using Microsoft.Extensions.Logging; +using QYZH.InteractiveMagazine.Common.Extensions; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using SqlSugar; @@ -28,11 +30,11 @@ public class CommunityMessageService(BaseRepository messageRep RefAsync totalNumber = 0; var pageResult = await messageRepository.Queryable() .WhereIF(input.JournalId.HasValue, m => m.JournalId == input.JournalId.Value) - .WhereIF(!string.IsNullOrWhiteSpace(input.Type), m => m.Type == input.Type) + .WhereIF(!string.IsNullOrWhiteSpace(input.Type), m => m.Type.ToString() == input.Type) .WhereIF(input.Status.HasValue, m => m.Status == input.Status.Value) .WhereIF(input.IsFeatured.HasValue, m => m.IsFeatured == input.IsFeatured.Value) .WhereIF(input.IsActive.HasValue, m => m.IsActive == input.IsActive.Value) - .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), m => m.Content.Contains(input.Keyword)) + .WhereIF(!string.IsNullOrWhiteSpace(input.KeyWord), m => m.Content.Contains(input.KeyWord)) .WhereIF(input.UserId.HasValue, m => m.UserId == input.UserId.Value) .OrderByDescending(m => m.IsFeatured) .OrderByDescending(m => m.CreatedAt) @@ -48,7 +50,7 @@ public class CommunityMessageService(BaseRepository messageRep ImageUrl = m.ImageUrl, SortOrder = m.SortOrder, IsActive = m.IsActive, - Type = m.Type, + Type = m.Type.ToString(), LikeCount = m.LikeCount, IsFeatured = m.IsFeatured, Status = m.Status, @@ -88,7 +90,7 @@ public class CommunityMessageService(BaseRepository messageRep ImageUrl = message.ImageUrl, SortOrder = message.SortOrder, IsActive = message.IsActive, - Type = message.Type, + Type = message.Type.ToString(), LikeCount = message.LikeCount, IsFeatured = message.IsFeatured, Status = message.Status, @@ -216,4 +218,24 @@ public class CommunityMessageService(BaseRepository messageRep logger.LogInformation("社区消息排序权重设置成功,ID: {Id}, SortOrder: {SortOrder}", id, sortOrder); } + + /// + /// 批量发布消息 + /// + public async Task BatchPublishAsync(List ids) + { + logger.LogInformation("正在批量发布社区消息,数量: {Count}", ids.Count); + + if (ids == null || ids.Count == 0) + { + throw new BusinessException("消息ID列表不能为空", 400); + } + + var result = await Context.Updateable() + .SetColumns(m => m.IsActive == true) + .Where(m => ids.Contains(m.Id)) + .ExecuteCommandAsync(); + + logger.LogInformation("批量发布社区消息完成,影响行数: {Result}", result); + } } diff --git a/QYZH.InteractiveMagazine.Service/CompensationManageService.cs b/QYZH.InteractiveMagazine.Service/CompensationManageService.cs new file mode 100644 index 0000000..9483eb3 --- /dev/null +++ b/QYZH.InteractiveMagazine.Service/CompensationManageService.cs @@ -0,0 +1,191 @@ +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.Compensation; +using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; +using QYZH.InteractiveMagazine.Repository; +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Service; + +/// +/// 补偿任务管理服务实现 +/// +public class CompensationManageService( + BaseRepository compensationTaskRepository, + IOperationLogService operationLogService, + ILogger logger) + : BaseRepository, ICompensationManageService +{ + /// + /// 分页查询补偿任务(含用户昵称) + /// + public async Task> GetListAsync(CompensationTaskQueryInput input) + { + if (input.PageIndex <= 0) + input.PageIndex = 1; + + if (input.PageSize <= 0 || input.PageSize > 100) + input.PageSize = 10; + + RefAsync totalNumber = 0; + + var pageResult = await compensationTaskRepository.Queryable() + .LeftJoin((t, u) => t.UserId == u.Id) + .WhereIF(input.UserId.HasValue, (t, u) => t.UserId == input.UserId.Value) + .WhereIF(input.TaskType.HasValue, (t, u) => t.TaskType == (int)input.TaskType.Value) + .WhereIF(input.Status.HasValue, (t, u) => t.Status == input.Status.Value) + .WhereIF(!string.IsNullOrWhiteSpace(input.BusinessSource), (t, u) => t.BusinessSource == input.BusinessSource) + .OrderByDescending((t, u) => t.CreatedAt) + .Select((t, u) => new CompensationManageOutput + { + Id = t.Id, + TaskType = (CompensationTaskTypeEnum)t.TaskType, + BusinessSource = t.BusinessSource, + BusinessId = t.BusinessId, + UserId = t.UserId, + UserName = u.Name, + Payload = t.Payload, + ErrorMessage = t.ErrorMessage, + ErrorSource = t.ErrorSource, + RetryCount = t.RetryCount, + MaxRetries = t.MaxRetries, + Status = t.Status, + ProcessedAt = t.ProcessedAt, + ScheduledAt = t.ScheduledAt, + ResultMessage = t.ResultMessage, + CreatedAt = t.CreatedAt + }) + .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); + + return new PageListModel(pageResult, input.PageIndex, input.PageSize, totalNumber); + } + + /// + /// 手动重试补偿任务 + /// + public async Task RetryAsync(long taskId, long operatorId, string operatorName, CompensationRetryInput input, string? ipAddress = null) + { + logger.LogInformation("手动重试补偿任务,TaskId: {TaskId}, Operator: {Operator}", taskId, operatorName); + + var task = await compensationTaskRepository.GetByIdAsync(taskId); + if (task == null) + throw new BusinessException("补偿任务不存在", 404); + + if (task.Status != CompensationTaskStatusEnum.Failed && task.Status != CompensationTaskStatusEnum.Cancelled) + throw new BusinessException($"只有失败或已取消的任务才能重试,当前状态: {task.Status}", 400); + + // 重置任务状态为 Pending,清零重试次数,设置立即执行 + await compensationTaskRepository.Context.Updateable() + .SetColumns(t => t.Status == CompensationTaskStatusEnum.Pending) + .SetColumns(t => t.RetryCount == 0) + .SetColumns(t => t.ScheduledAt == DateTime.Now) + .SetColumns(t => t.ResultMessage == $"管理员手动重试: {input.Reason}") + .SetColumns(t => t.UpdatedBy == operatorName) + .SetColumns(t => t.UpdatedAt == DateTime.Now) + .Where(t => t.Id == taskId && !t.IsDeleted) + .ExecuteCommandAsync(); + + // 记录操作日志 + var detail = JsonConvert.SerializeObject(new + { + OriginalStatus = task.Status, + Reason = input.Reason, + TaskType = task.TaskType, + UserId = task.UserId + }); + + await operationLogService.LogAsync( + operatorId, operatorName, + OperationLogActionType.CompensationRetry, + OperationLogTargetType.CompensationTask, + taskId, null, detail, ipAddress); + + logger.LogInformation("补偿任务手动重试成功,TaskId: {TaskId}", taskId); + } + + /// + /// 标记补偿任务为已解决 + /// + public async Task ResolveAsync(long taskId, long operatorId, string operatorName, CompensationResolveInput input, string? ipAddress = null) + { + logger.LogInformation("标记补偿任务已解决,TaskId: {TaskId}, Operator: {Operator}", taskId, operatorName); + + var task = await compensationTaskRepository.GetByIdAsync(taskId); + if (task == null) + throw new BusinessException("补偿任务不存在", 404); + + if (task.Status == CompensationTaskStatusEnum.Success) + throw new BusinessException("该任务已经是成功状态,无需标记", 400); + + // 标记为 Success + await compensationTaskRepository.Context.Updateable() + .SetColumns(t => t.Status == CompensationTaskStatusEnum.Success) + .SetColumns(t => t.ResultMessage == $"管理员手动标记已解决: {input.ResolveNote}") + .SetColumns(t => t.ProcessedAt == DateTime.Now) + .SetColumns(t => t.UpdatedBy == operatorName) + .SetColumns(t => t.UpdatedAt == DateTime.Now) + .Where(t => t.Id == taskId && !t.IsDeleted) + .ExecuteCommandAsync(); + + // 记录操作日志 + var detail = JsonConvert.SerializeObject(new + { + OriginalStatus = task.Status, + ResolveNote = input.ResolveNote, + TaskType = task.TaskType, + UserId = task.UserId + }); + + await operationLogService.LogAsync( + operatorId, operatorName, + OperationLogActionType.CompensationResolve, + OperationLogTargetType.CompensationTask, + taskId, null, detail, ipAddress); + + logger.LogInformation("补偿任务标记已解决成功,TaskId: {TaskId}", taskId); + } + + /// + /// 获取补偿任务详情 + /// + public async Task GetDetailAsync(long taskId) + { + logger.LogInformation("获取补偿任务详情,TaskId: {TaskId}", taskId); + + var result = await compensationTaskRepository.Queryable() + .LeftJoin((t, u) => t.UserId == u.Id) + .Where((t, u) => t.Id == taskId && !t.IsDeleted) + .Select((t, u) => new CompensationManageDetailOutput + { + Id = t.Id, + TaskType = (CompensationTaskTypeEnum)t.TaskType, + BusinessSource = t.BusinessSource, + BusinessId = t.BusinessId, + UserId = t.UserId, + UserName = u.Name, + Payload = t.Payload, + ErrorMessage = t.ErrorMessage, + ErrorSource = t.ErrorSource, + RetryCount = t.RetryCount, + MaxRetries = t.MaxRetries, + Status = t.Status, + ProcessedAt = t.ProcessedAt, + ScheduledAt = t.ScheduledAt, + ResultMessage = t.ResultMessage, + CreatedAt = t.CreatedAt, + CreatedBy = t.CreatedBy, + UpdatedBy = t.UpdatedBy, + UpdatedAt = t.UpdatedAt + }) + .FirstAsync(); + + if (result == null) + throw new BusinessException("补偿任务不存在", 404); + + return result; + } +} diff --git a/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs b/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs index 4713198..be8e38d 100644 --- a/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs +++ b/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Dto.Compensation; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; namespace QYZH.InteractiveMagazine.Service; @@ -28,7 +29,7 @@ public class CompensationTaskService( var task = new CompensationTask { - TaskType = input.TaskType, + TaskType = (int)input.TaskType, BusinessSource = input.BusinessSource, BusinessId = input.BusinessId, UserId = input.UserId, @@ -37,7 +38,7 @@ public class CompensationTaskService( ErrorSource = input.ErrorSource, RetryCount = 0, MaxRetries = input.MaxRetries > 0 ? input.MaxRetries : 3, - Status = CompensationTaskStatus.Pending, + Status = CompensationTaskStatusEnum.Pending, ScheduledAt = DateTime.Now, IsDeleted = false, CreatedBy = "System", @@ -61,7 +62,7 @@ public class CompensationTaskService( var now = DateTime.Now; var tasks = await taskRepository.Queryable() - .Where(t => (t.Status == CompensationTaskStatus.Pending || t.Status == CompensationTaskStatus.Processing) + .Where(t => (t.Status == CompensationTaskStatusEnum.Pending || t.Status == CompensationTaskStatusEnum.Processing) && !t.IsDeleted && (t.ScheduledAt == null || t.ScheduledAt <= now)) .OrderBy(t => t.CreatedAt) @@ -69,7 +70,7 @@ public class CompensationTaskService( .Select(t => new CompensationTaskOutput { Id = t.Id, - TaskType = t.TaskType, + TaskType = (CompensationTaskTypeEnum)t.TaskType, BusinessSource = t.BusinessSource, BusinessId = t.BusinessId, UserId = t.UserId, @@ -97,11 +98,11 @@ public class CompensationTaskService( var query = taskRepository.Queryable() .Where(t => !t.IsDeleted); - if (!string.IsNullOrEmpty(input.Status)) - query = query.Where(t => t.Status == input.Status); + if (input.Status.HasValue) + query = query.Where(t => t.Status == input.Status.Value); - if (!string.IsNullOrEmpty(input.TaskType)) - query = query.Where(t => t.TaskType == input.TaskType); + if (input.TaskType.HasValue) + query = query.Where(t => t.TaskType == (int)input.TaskType.Value); if (!string.IsNullOrEmpty(input.BusinessSource)) query = query.Where(t => t.BusinessSource == input.BusinessSource); @@ -112,7 +113,7 @@ public class CompensationTaskService( .Select(t => new CompensationTaskOutput { Id = t.Id, - TaskType = t.TaskType, + TaskType = (CompensationTaskTypeEnum)t.TaskType, BusinessSource = t.BusinessSource, BusinessId = t.BusinessId, UserId = t.UserId, @@ -162,7 +163,7 @@ public class CompensationTaskService( public async Task CancelTaskAsync(long taskId, string reason) { await taskRepository.Context.Updateable() - .SetColumns(t => t.Status == CompensationTaskStatus.Cancelled) + .SetColumns(t => t.Status == CompensationTaskStatusEnum.Cancelled) .SetColumns(t => t.ResultMessage == reason) .SetColumns(t => t.UpdatedAt == DateTime.Now) .Where(t => t.Id == taskId && !t.IsDeleted) diff --git a/QYZH.InteractiveMagazine.Service/MedalService.cs b/QYZH.InteractiveMagazine.Service/MedalService.cs index 782bbf9..1fa2c55 100644 --- a/QYZH.InteractiveMagazine.Service/MedalService.cs +++ b/QYZH.InteractiveMagazine.Service/MedalService.cs @@ -4,6 +4,7 @@ using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using SqlSugar; @@ -38,7 +39,7 @@ public class MedalService(BaseRepository medalRepository, ILogger(input.Type, true), JournalId = input.JournalId, CreatedBy = "System", UpdatedBy = "System", @@ -107,7 +108,7 @@ public class MedalService(BaseRepository medalRepository, ILogger(input.Type, true); medal.JournalId = input.JournalId; medal.UpdatedBy = "System"; medal.UpdatedAt = DateTime.Now; @@ -232,7 +233,7 @@ public class MedalService(BaseRepository medalRepository, ILogger totalNumber = 0; var medals = await medalRepository.Queryable() .WhereIF(!string.IsNullOrWhiteSpace(input.Name), m => m.Name.Contains(input.Name)) - .WhereIF(!string.IsNullOrWhiteSpace(input.Type), m => m.Type == input.Type) + .WhereIF(!string.IsNullOrWhiteSpace(input.Type), m => m.Type.ToString() == input.Type) .OrderBy(m => m.SortOrder) .OrderByDescending(m => m.CreatedAt) .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); @@ -313,7 +314,7 @@ public class MedalService(BaseRepository medalRepository, ILogger() - .Where(um => um.UserId == userId && um.Status == "Awarded") + .Where(um => um.UserId == userId && um.Status == UserMedalStatusEnum.Awarded) .ToListAsync(); var userMedalDict = userMedals.ToDictionary(um => um.MedalId, um => um.AwardedAt); @@ -325,7 +326,7 @@ public class MedalService(BaseRepository medalRepository, ILogger medalRepository, ILogger() .InnerJoin((um, m) => um.MedalId == m.Id) - .Where((um, m) => um.UserId == userId && um.Status == "Awarded") + .Where((um, m) => um.UserId == userId && um.Status == UserMedalStatusEnum.Awarded) .OrderByDescending((um, m) => um.AwardedAt) .Select((um, m) => new WxUserMedalOutput { @@ -348,9 +349,9 @@ public class MedalService(BaseRepository medalRepository, ILogger medalRepository, ILogger() - .Where(um => um.UserId == userId && um.MedalId == (int)input.MedalId && um.Status == "Awarded") + .Where(um => um.UserId == userId && um.MedalId == (int)input.MedalId && um.Status == UserMedalStatusEnum.Awarded) .FirstAsync(); if (existingUserMedal != null) @@ -403,8 +404,8 @@ public class MedalService(BaseRepository medalRepository, ILogger medalRepository, ILogger +/// 操作日志服务实现 +/// +public class OperationLogService( + BaseRepository operationLogRepository, + BaseRepository adminUserRepository, + BaseRepository usersRepository, + ILogger logger) : BaseRepository, IOperationLogService +{ + + + /// + /// 记录操作日志 + /// + public async Task LogAsync(long operatorId, string operatorName, string actionType, string targetType, long targetId, string? targetName = null, string? detail = null, string? ipAddress = null) + { + try + { + var log = new OperationLog + { + OperatorId = operatorId, + OperatorName = operatorName, + ActionType = actionType, + TargetType = targetType, + TargetId = targetId, + TargetName = targetName, + Detail = detail, + IpAddress = ipAddress, + IsDeleted = false, + CreatedBy = operatorName, + CreatedAt = DateTime.Now, + UpdatedBy = operatorName, + UpdatedAt = DateTime.Now + }; + + await operationLogRepository.InsertAsync(log); + + logger.LogInformation( + "记录操作日志,Operator: {Operator}, Action: {Action}, Target: {TargetType}/{TargetId}", + operatorName, actionType, targetType, targetId); + } + catch (Exception ex) + { + // 日志记录不应影响主业务流程 + logger.LogError(ex, "记录操作日志失败,Operator: {Operator}, Action: {Action}", operatorName, actionType); + } + } + + /// + /// 分页查询操作日志 + /// + public async Task> GetListAsync(OperationLogQueryInput input) + { + if (input.PageIndex <= 0) + input.PageIndex = 1; + + if (input.PageSize <= 0 || input.PageSize > 100) + input.PageSize = 10; + + RefAsync totalNumber = 0; + + var pageResult = await operationLogRepository.Queryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.OperatorName), l => l.OperatorName.Contains(input.OperatorName)) + .WhereIF(!string.IsNullOrWhiteSpace(input.ActionType), l => l.ActionType == input.ActionType) + .WhereIF(!string.IsNullOrWhiteSpace(input.TargetType), l => l.TargetType == input.TargetType) + .WhereIF(input.TargetId.HasValue, l => l.TargetId == input.TargetId.Value) + .OrderByDescending(l => l.CreatedAt) + .Select(l => new OperationLogOutput + { + Id = l.Id, + OperatorId = l.OperatorId, + OperatorName = l.OperatorName, + ActionType = l.ActionType, + TargetType = l.TargetType, + TargetId = l.TargetId, + TargetName = l.TargetName, + Detail = l.Detail, + IpAddress = l.IpAddress, + CreatedAt = l.CreatedAt + }) + .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); + + return new PageListModel(pageResult, input.PageIndex, input.PageSize, totalNumber); + } + + /// + /// 获取操作日志详情 + /// + public async Task GetDetailAsync(long id) + { + var log = await operationLogRepository.Queryable() + .Where(l => l.Id == id && !l.IsDeleted) + .FirstAsync(); + + if (log == null) + { + throw new BusinessException("操作日志记录不存在"); + } + + var result = new OperationLogDetailOutput + { + Id = log.Id, + ActionType = log.ActionType, + TargetType = log.TargetType, + TargetId = log.TargetId, + TargetName = log.TargetName, + Detail = log.Detail, + IpAddress = log.IpAddress, + CreatedAt = log.CreatedAt, + OperatorName = log.OperatorName + }; + + // 查询操作人详细信息 + var adminUser = await adminUserRepository.GetByIdAsync(log.OperatorId); + if (adminUser != null) + { + result.OperatorRole = adminUser.Type.ToString(); + } + + // 当目标类型为用户时,查询被操作人信息 + if (log.TargetType == OperationLogTargetType.User) + { + var targetUser = await usersRepository.GetByIdAsync(log.TargetId); + if (targetUser != null) + { + result.TargetUserName = targetUser.Name; + result.TargetUserPhone = targetUser.Phone; + result.TargetUserAvatar = targetUser.AvatarUrl; + } + } + + return result; + } +} diff --git a/QYZH.InteractiveMagazine.Service/PetService.cs b/QYZH.InteractiveMagazine.Service/PetService.cs index b45c6d0..247f2e7 100644 --- a/QYZH.InteractiveMagazine.Service/PetService.cs +++ b/QYZH.InteractiveMagazine.Service/PetService.cs @@ -4,6 +4,7 @@ using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Dto.Pet; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using SqlSugar; @@ -84,8 +85,8 @@ public class PetService( CurrentSkinId = pet.CurrentSkinId, CurrentSkinName = skinName, CurrentImages = images, - Type = pet.Type, - Status = pet.Status, + Type = pet.Type.ToString(), + Status = pet.Status.ToString(), CreatedAt = pet.CreatedAt }; } @@ -108,7 +109,7 @@ public class PetService( // 查询默认宠物模板(取排序权重最低且状态为Active的模板) var defaultTemplate = await petTemplateRepository.Queryable() - .Where(t => t.Status == "Active" && !t.IsDeleted) + .Where(t => t.Status == PetTemplateStatusEnum.Active && !t.IsDeleted) .OrderBy(t => t.SortOrder) .FirstAsync(); @@ -122,7 +123,7 @@ public class PetService( var initialEvolution = await petEvolutionRepository.Queryable() .Where(e => e.TemplateId == defaultTemplate.Id && e.PreviousEvolutionId == null - && e.Status == "Active") + && e.Status == PetEvolutionStatusEnum.Active) .OrderBy(e => e.StageLevel) .FirstAsync(); @@ -135,8 +136,8 @@ public class PetService( GrowthPoints = 0, FeedingCount = 0, CurrentSkinId = 0, - Type = "Normal", - Status = "Inactive", + Type = UserPetTypeEnum.Normal, + Status = UserPetStatusEnum.Inactive, IsDeleted = false, CreatedBy = userId.ToString(), CreatedAt = DateTime.Now, @@ -172,14 +173,14 @@ public class PetService( return; } - if (pet.Status != "Inactive") + if (pet.Status != UserPetStatusEnum.Inactive) { logger.LogInformation("用户宠物已非未激活状态,跳过激活,UserId: {UserId}, Status: {Status}", userId, pet.Status); return; } var result = await petRepository.UpdateAsync( - p => new UserPet { Status = "Active" }, + p => new UserPet { Status = UserPetStatusEnum.Active }, p => p.UserId == userId); if (!result) @@ -225,7 +226,7 @@ public class PetService( } // 校验宠物状态 - if (pet.Status != "Active") + if (pet.Status != UserPetStatusEnum.Active) { logger.LogWarning("喂养失败,宠物未激活,PetId: {PetId}, Status: {Status}", input.PetId, pet.Status); throw new BusinessException("宠物未激活,无法喂养", 400); @@ -257,7 +258,7 @@ public class PetService( var nextEvolution = await petEvolutionRepository.Queryable() .Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId && e.RequiredGrowth <= growthAfter - && e.Status == "Active") + && e.Status == PetEvolutionStatusEnum.Active) .OrderBy(e => e.RequiredGrowth, OrderByType.Desc) .FirstAsync(); @@ -289,8 +290,8 @@ public class PetService( GrowthChange = input.GrowthPoints, GrowthBefore = growthBefore, GrowthAfter = growthAfter, - Type = "Normal", - Status = "Success", + Type = PetFeedingRecordTypeEnum.Normal, + Status = PetFeedingRecordStatusEnum.Success, IsDeleted = false, CreatedBy = userId.ToString(), CreatedAt = DateTime.Now, @@ -339,12 +340,663 @@ public class PetService( GrowthChange = r.GrowthChange, GrowthBefore = r.GrowthBefore, GrowthAfter = r.GrowthAfter, - Type = r.Type, - Status = r.Status, + Type = r.Type.ToString(), + Status = r.Status.ToString(), CreatedAt = r.CreatedAt }, true) .ToPageListAsync(pageQuery.PageIndex, pageQuery.PageSize, totalNumber); return new PageListModel(records, pageQuery.PageIndex, pageQuery.PageSize, totalNumber); } + + // ==================== 后台管理:宠物模板 ==================== + + /// + /// 创建宠物模板 + /// + public async Task CreateTemplateAsync(PetTemplateInput input) + { + if (string.IsNullOrWhiteSpace(input.Name)) + throw new BusinessException("模板名称不能为空", 400); + + var template = new PetTemplate + { + Name = input.Name.Trim(), + Description = input.Description, + DefaultEvolutionId = input.DefaultEvolutionId, + IconUrl = input.IconUrl, + SortOrder = input.SortOrder, + Type = Enum.Parse(input.Type, true), + Status = PetTemplateStatusEnum.Active, + CreatedBy = "System", + CreatedAt = DateTime.Now, + UpdatedBy = "System", + UpdatedAt = DateTime.Now, + IsDeleted = false + }; + + var result = await petTemplateRepository.InsertAsync(template); + if (!result) + throw new BusinessException("创建宠物模板失败", 500); + + logger.LogInformation("创建宠物模板成功,Id: {Id}, Name: {Name}", template.Id, template.Name); + return BuildTemplateOutput(template); + } + + /// + /// 更新宠物模板 + /// + public async Task UpdateTemplateAsync(long id, PetTemplateInput input) + { + var template = await petTemplateRepository.GetByIdAsync(id); + if (template == null || template.IsDeleted) + throw new BusinessException("宠物模板不存在", 404); + + if (string.IsNullOrWhiteSpace(input.Name)) + throw new BusinessException("模板名称不能为空", 400); + + template.Name = input.Name.Trim(); + template.Description = input.Description; + template.DefaultEvolutionId = input.DefaultEvolutionId; + template.IconUrl = input.IconUrl; + template.SortOrder = input.SortOrder; + template.Type = Enum.Parse(input.Type, true); + template.UpdatedBy = "System"; + template.UpdatedAt = DateTime.Now; + + var result = await petTemplateRepository.UpdateAsync(template); + if (!result) + throw new BusinessException("更新宠物模板失败", 500); + + logger.LogInformation("更新宠物模板成功,Id: {Id}", id); + return BuildTemplateOutput(template); + } + + /// + /// 删除宠物模板(软删除,校验是否有用户宠物关联) + /// + public async Task DeleteTemplateAsync(long id) + { + var template = await petTemplateRepository.GetByIdAsync(id); + if (template == null || template.IsDeleted) + throw new BusinessException("宠物模板不存在", 404); + + // 校验是否有用户宠物实例关联 + var hasUserPet = petRepository.Context.Queryable() + .Any(p => p.TemplateId == id && !p.IsDeleted); + if (hasUserPet) + throw new BusinessException("该模板下存在用户宠物实例,无法删除", 400); + + template.IsDeleted = true; + template.UpdatedBy = "System"; + template.UpdatedAt = DateTime.Now; + await petTemplateRepository.UpdateAsync(template); + + logger.LogInformation("删除宠物模板成功,Id: {Id}", id); + } + + /// + /// 获取单个宠物模板 + /// + public async Task GetTemplateByIdAsync(long id) + { + var template = await petTemplateRepository.GetByIdAsync(id); + if (template == null || template.IsDeleted) + throw new BusinessException("宠物模板不存在", 404); + + return BuildTemplateOutput(template); + } + + /// + /// 分页查询宠物模板列表 + /// + public async Task> GetTemplatesAsync(PetTemplateQueryInput input) + { + RefAsync totalNumber = 0; + var query = petTemplateRepository.Queryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), t => t.Name.Contains(input.Name)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Type), t => t.Type.ToString() == input.Type) + .WhereIF(!string.IsNullOrWhiteSpace(input.Status), t => t.Status.ToString() == input.Status) + .OrderBy(t => t.SortOrder) + .OrderByDescending(t => t.CreatedAt); + + var list = await query + .Select(t => new PetTemplateOutput + { + Id = t.Id, + Name = t.Name, + Description = t.Description, + DefaultEvolutionId = t.DefaultEvolutionId, + IconUrl = t.IconUrl, + SortOrder = t.SortOrder, + Type = t.Type.ToString(), + Status = t.Status.ToString(), + CreatedBy = t.CreatedBy, + CreatedAt = t.CreatedAt, + UpdatedBy = t.UpdatedBy, + UpdatedAt = t.UpdatedAt + }) + .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); + + return new PageListModel(list, input.PageIndex, input.PageSize, totalNumber); + } + + /// + /// 更新模板状态(启用/禁用) + /// + public async Task UpdateTemplateStatusAsync(long id, string status) + { + var template = await petTemplateRepository.GetByIdAsync(id); + if (template == null || template.IsDeleted) + throw new BusinessException("宠物模板不存在", 404); + + template.Status = Enum.Parse(status, true); + template.UpdatedBy = "System"; + template.UpdatedAt = DateTime.Now; + await petTemplateRepository.UpdateAsync(template); + + logger.LogInformation("更新模板状态成功,Id: {Id}, Status: {Status}", id, status); + } + + private static PetTemplateOutput BuildTemplateOutput(PetTemplate t) + { + return new PetTemplateOutput + { + Id = t.Id, + Name = t.Name, + Description = t.Description, + DefaultEvolutionId = t.DefaultEvolutionId, + IconUrl = t.IconUrl, + SortOrder = t.SortOrder, + Type = t.Type.ToString(), + Status = t.Status.ToString(), + CreatedBy = t.CreatedBy, + CreatedAt = t.CreatedAt, + UpdatedBy = t.UpdatedBy, + UpdatedAt = t.UpdatedAt + }; + } + + // ==================== 后台管理:进化链 ==================== + + /// + /// 创建进化阶段 + /// + public async Task CreateEvolutionAsync(PetEvolutionInput input) + { + if (input.TemplateId <= 0) + throw new BusinessException("模板Id不能为空", 400); + + if (string.IsNullOrWhiteSpace(input.StageName)) + throw new BusinessException("阶段名称不能为空", 400); + + // 校验模板是否存在 + var templateExists = petTemplateRepository.Context.Queryable() + .Any(t => t.Id == input.TemplateId && !t.IsDeleted); + if (!templateExists) + throw new BusinessException("宠物模板不存在", 404); + + var evolution = new PetEvolution + { + TemplateId = input.TemplateId, + StageName = input.StageName.Trim(), + StageLevel = input.StageLevel, + RequiredGrowth = input.RequiredGrowth, + PreviousEvolutionId = input.PreviousEvolutionId, + BaseStrength = input.BaseStrength, + BaseAgility = input.BaseAgility, + BaseIntelligence = input.BaseIntelligence, + BaseCharm = input.BaseCharm, + Type = Enum.Parse(input.Type, true), + Status = PetEvolutionStatusEnum.Active, + CreatedBy = "System", + CreatedAt = DateTime.Now, + UpdatedBy = "System", + UpdatedAt = DateTime.Now, + IsDeleted = false + }; + + var result = await petEvolutionRepository.InsertAsync(evolution); + if (!result) + throw new BusinessException("创建进化阶段失败", 500); + + logger.LogInformation("创建进化阶段成功,Id: {Id}, StageName: {StageName}", evolution.Id, evolution.StageName); + return BuildEvolutionOutput(evolution); + } + + /// + /// 更新进化阶段 + /// + public async Task UpdateEvolutionAsync(long id, PetEvolutionInput input) + { + var evolution = await petEvolutionRepository.GetByIdAsync(id); + if (evolution == null || evolution.IsDeleted) + throw new BusinessException("进化阶段不存在", 404); + + if (string.IsNullOrWhiteSpace(input.StageName)) + throw new BusinessException("阶段名称不能为空", 400); + + evolution.TemplateId = input.TemplateId; + evolution.StageName = input.StageName.Trim(); + evolution.StageLevel = input.StageLevel; + evolution.RequiredGrowth = input.RequiredGrowth; + evolution.PreviousEvolutionId = input.PreviousEvolutionId; + evolution.BaseStrength = input.BaseStrength; + evolution.BaseAgility = input.BaseAgility; + evolution.BaseIntelligence = input.BaseIntelligence; + evolution.BaseCharm = input.BaseCharm; + evolution.Type = Enum.Parse(input.Type, true); + evolution.UpdatedBy = "System"; + evolution.UpdatedAt = DateTime.Now; + + var result = await petEvolutionRepository.UpdateAsync(evolution); + if (!result) + throw new BusinessException("更新进化阶段失败", 500); + + logger.LogInformation("更新进化阶段成功,Id: {Id}", id); + return BuildEvolutionOutput(evolution); + } + + /// + /// 删除进化阶段(软删除,校验是否有用户宠物处于该形态) + /// + public async Task DeleteEvolutionAsync(long id) + { + var evolution = await petEvolutionRepository.GetByIdAsync(id); + if (evolution == null || evolution.IsDeleted) + throw new BusinessException("进化阶段不存在", 404); + + // 校验是否有用户宠物处于该形态 + var hasUserPet = petRepository.Context.Queryable() + .Any(p => p.CurrentEvolutionId == id && !p.IsDeleted); + if (hasUserPet) + throw new BusinessException("有用户宠物正处于该形态,无法删除", 400); + + evolution.IsDeleted = true; + evolution.UpdatedBy = "System"; + evolution.UpdatedAt = DateTime.Now; + await petEvolutionRepository.UpdateAsync(evolution); + + logger.LogInformation("删除进化阶段成功,Id: {Id}", id); + } + + /// + /// 获取单个进化阶段 + /// + public async Task GetEvolutionByIdAsync(long id) + { + var evolution = await petEvolutionRepository.GetByIdAsync(id); + if (evolution == null || evolution.IsDeleted) + throw new BusinessException("进化阶段不存在", 404); + + return BuildEvolutionOutput(evolution); + } + + /// + /// 分页查询进化阶段列表 + /// + public async Task> GetEvolutionsAsync(PetEvolutionQueryInput input) + { + RefAsync totalNumber = 0; + var query = petEvolutionRepository.Queryable() + .Where(e => e.TemplateId == input.TemplateId) + .WhereIF(!string.IsNullOrWhiteSpace(input.StageName), e => e.StageName.Contains(input.StageName)) + .OrderBy(e => e.StageLevel); + + var list = await query + .Select(e => new PetEvolutionOutput + { + Id = e.Id, + TemplateId = e.TemplateId, + StageName = e.StageName, + StageLevel = e.StageLevel, + RequiredGrowth = e.RequiredGrowth, + PreviousEvolutionId = e.PreviousEvolutionId, + BaseStrength = e.BaseStrength, + BaseAgility = e.BaseAgility, + BaseIntelligence = e.BaseIntelligence, + BaseCharm = e.BaseCharm, + Type = e.Type.ToString(), + Status = e.Status.ToString(), + CreatedBy = e.CreatedBy, + CreatedAt = e.CreatedAt, + UpdatedBy = e.UpdatedBy, + UpdatedAt = e.UpdatedAt + }) + .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); + + return new PageListModel(list, input.PageIndex, input.PageSize, totalNumber); + } + + private static PetEvolutionOutput BuildEvolutionOutput(PetEvolution e) + { + return new PetEvolutionOutput + { + Id = e.Id, + TemplateId = e.TemplateId, + StageName = e.StageName, + StageLevel = e.StageLevel, + RequiredGrowth = e.RequiredGrowth, + PreviousEvolutionId = e.PreviousEvolutionId, + BaseStrength = e.BaseStrength, + BaseAgility = e.BaseAgility, + BaseIntelligence = e.BaseIntelligence, + BaseCharm = e.BaseCharm, + Type = e.Type.ToString(), + Status = e.Status.ToString(), + CreatedBy = e.CreatedBy, + CreatedAt = e.CreatedAt, + UpdatedBy = e.UpdatedBy, + UpdatedAt = e.UpdatedAt + }; + } + + // ==================== 后台管理:皮肤 ==================== + + /// + /// 创建皮肤 + /// + public async Task CreateSkinAsync(PetSkinInput input) + { + if (input.TemplateId <= 0) + throw new BusinessException("模板Id不能为空", 400); + + if (string.IsNullOrWhiteSpace(input.Name)) + throw new BusinessException("皮肤名称不能为空", 400); + + // 校验模板是否存在 + var templateExists = petTemplateRepository.Context.Queryable() + .Any(t => t.Id == input.TemplateId && !t.IsDeleted); + if (!templateExists) + throw new BusinessException("宠物模板不存在", 404); + + var skin = new PetSkin + { + TemplateId = input.TemplateId, + Name = input.Name.Trim(), + Description = input.Description, + Rarity = input.Rarity, + SortOrder = input.SortOrder, + Type = Enum.Parse(input.Type, true), + CreatedBy = "System", + CreatedAt = DateTime.Now, + UpdatedBy = "System", + UpdatedAt = DateTime.Now, + IsDeleted = false + }; + + var result = await petSkinRepository.InsertAsync(skin); + if (!result) + throw new BusinessException("创建皮肤失败", 500); + + logger.LogInformation("创建皮肤成功,Id: {Id}, Name: {Name}", skin.Id, skin.Name); + return BuildSkinOutput(skin, null); + } + + /// + /// 更新皮肤 + /// + public async Task UpdateSkinAsync(long id, PetSkinInput input) + { + var skin = await petSkinRepository.GetByIdAsync(id); + if (skin == null || skin.IsDeleted) + throw new BusinessException("皮肤不存在", 404); + + if (string.IsNullOrWhiteSpace(input.Name)) + throw new BusinessException("皮肤名称不能为空", 400); + + skin.TemplateId = input.TemplateId; + skin.Name = input.Name.Trim(); + skin.Description = input.Description; + skin.Rarity = input.Rarity; + skin.SortOrder = input.SortOrder; + skin.Type = Enum.Parse(input.Type, true); + skin.UpdatedBy = "System"; + skin.UpdatedAt = DateTime.Now; + + var result = await petSkinRepository.UpdateAsync(skin); + if (!result) + throw new BusinessException("更新皮肤失败", 500); + + logger.LogInformation("更新皮肤成功,Id: {Id}", id); + + // 查询关联图片 + var images = await petSkinImageRepository.Queryable() + .Where(i => i.SkinId == id && !i.IsDeleted) + .OrderBy(i => i.SortOrder) + .ToListAsync(); + + return BuildSkinOutput(skin, images); + } + + /// + /// 删除皮肤(软删除,校验是否有用户宠物装备中) + /// + public async Task DeleteSkinAsync(long id) + { + var skin = await petSkinRepository.GetByIdAsync(id); + if (skin == null || skin.IsDeleted) + throw new BusinessException("皮肤不存在", 404); + + // 校验是否有用户宠物正在使用该皮肤 + var inUse = petRepository.Context.Queryable() + .Any(p => p.CurrentSkinId == id && !p.IsDeleted); + if (inUse) + throw new BusinessException("有用户宠物正在使用该皮肤,无法删除", 400); + + skin.IsDeleted = true; + skin.UpdatedBy = "System"; + skin.UpdatedAt = DateTime.Now; + await petSkinRepository.UpdateAsync(skin); + + logger.LogInformation("删除皮肤成功,Id: {Id}", id); + } + + /// + /// 获取单个皮肤(含图片列表) + /// + public async Task GetSkinByIdAsync(long id) + { + var skin = await petSkinRepository.GetByIdAsync(id); + if (skin == null || skin.IsDeleted) + throw new BusinessException("皮肤不存在", 404); + + var images = await petSkinImageRepository.Queryable() + .Where(i => i.SkinId == id && !i.IsDeleted) + .OrderBy(i => i.SortOrder) + .ToListAsync(); + + return BuildSkinOutput(skin, images); + } + + /// + /// 分页查询皮肤列表 + /// + public async Task> GetSkinsAsync(PetSkinQueryInput input) + { + RefAsync totalNumber = 0; + var query = petSkinRepository.Queryable() + .Where(s => s.TemplateId == input.TemplateId) + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), s => s.Name.Contains(input.Name)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Rarity), s => s.Rarity == input.Rarity) + .OrderBy(s => s.SortOrder) + .OrderByDescending(s => s.CreatedAt); + + var list = await query + .Select(s => new PetSkinOutput + { + Id = s.Id, + TemplateId = s.TemplateId, + Name = s.Name, + Description = s.Description, + Rarity = s.Rarity, + SortOrder = s.SortOrder, + Type = s.Type.ToString(), + CreatedBy = s.CreatedBy, + CreatedAt = s.CreatedAt, + UpdatedBy = s.UpdatedBy, + UpdatedAt = s.UpdatedAt + }) + .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); + + // 批量查询预览图片 + var skinIds = list.Select(s => s.Id).ToList(); + if (skinIds.Count > 0) + { + var allImages = await petSkinImageRepository.Queryable() + .Where(i => skinIds.Contains(i.SkinId) && !i.IsDeleted) + .OrderBy(i => i.SortOrder) + .ToListAsync(); + + var imageMap = allImages + .GroupBy(i => i.SkinId) + .ToDictionary(g => g.Key, g => g.Select(i => BuildSkinImageOutput(i)).ToList()); + + foreach (var skin in list) + { + if (imageMap.ContainsKey(skin.Id)) + skin.Images = imageMap[skin.Id]; + } + } + + return new PageListModel(list, input.PageIndex, input.PageSize, totalNumber); + } + + private static PetSkinOutput BuildSkinOutput(PetSkin s, List? images) + { + return new PetSkinOutput + { + Id = s.Id, + TemplateId = s.TemplateId, + Name = s.Name, + Description = s.Description, + Rarity = s.Rarity, + SortOrder = s.SortOrder, + Type = s.Type.ToString(), + CreatedBy = s.CreatedBy, + CreatedAt = s.CreatedAt, + UpdatedBy = s.UpdatedBy, + UpdatedAt = s.UpdatedAt, + Images = images?.Select(BuildSkinImageOutput).ToList() + }; + } + + // ==================== 后台管理:皮肤图片 ==================== + + /// + /// 创建皮肤图片 + /// + public async Task CreateSkinImageAsync(PetSkinImageInput input) + { + if (input.SkinId <= 0) + throw new BusinessException("皮肤Id不能为空", 400); + + if (input.EvolutionStageId <= 0) + throw new BusinessException("进化阶段Id不能为空", 400); + + if (string.IsNullOrWhiteSpace(input.ImageUrl)) + throw new BusinessException("图片地址不能为空", 400); + + // 校验皮肤是否存在 + var skinExists = petSkinRepository.Context.Queryable() + .Any(s => s.Id == input.SkinId && !s.IsDeleted); + if (!skinExists) + throw new BusinessException("皮肤不存在", 404); + + var image = new PetSkinImage + { + SkinId = input.SkinId, + EvolutionStageId = input.EvolutionStageId, + ImageUrl = input.ImageUrl.Trim(), + SortOrder = input.SortOrder, + Type = Enum.Parse(input.Type, true), + CreatedBy = "System", + CreatedAt = DateTime.Now, + UpdatedBy = "System", + UpdatedAt = DateTime.Now, + IsDeleted = false + }; + + var result = await petSkinImageRepository.InsertAsync(image); + if (!result) + throw new BusinessException("创建皮肤图片失败", 500); + + logger.LogInformation("创建皮肤图片成功,Id: {Id}, SkinId: {SkinId}", image.Id, image.SkinId); + return BuildSkinImageOutput(image); + } + + /// + /// 更新皮肤图片 + /// + public async Task UpdateSkinImageAsync(long id, PetSkinImageInput input) + { + var image = await petSkinImageRepository.GetByIdAsync(id); + if (image == null || image.IsDeleted) + throw new BusinessException("皮肤图片不存在", 404); + + if (string.IsNullOrWhiteSpace(input.ImageUrl)) + throw new BusinessException("图片地址不能为空", 400); + + image.SkinId = input.SkinId; + image.EvolutionStageId = input.EvolutionStageId; + image.ImageUrl = input.ImageUrl.Trim(); + image.SortOrder = input.SortOrder; + image.Type = Enum.Parse(input.Type, true); + image.UpdatedBy = "System"; + image.UpdatedAt = DateTime.Now; + + var result = await petSkinImageRepository.UpdateAsync(image); + if (!result) + throw new BusinessException("更新皮肤图片失败", 500); + + logger.LogInformation("更新皮肤图片成功,Id: {Id}", id); + return BuildSkinImageOutput(image); + } + + /// + /// 删除皮肤图片(软删除) + /// + public async Task DeleteSkinImageAsync(long id) + { + var image = await petSkinImageRepository.GetByIdAsync(id); + if (image == null || image.IsDeleted) + throw new BusinessException("皮肤图片不存在", 404); + + image.IsDeleted = true; + image.UpdatedBy = "System"; + image.UpdatedAt = DateTime.Now; + await petSkinImageRepository.UpdateAsync(image); + + logger.LogInformation("删除皮肤图片成功,Id: {Id}", id); + } + + /// + /// 获取指定皮肤的所有图片 + /// + public async Task> GetSkinImagesBySkinIdAsync(long skinId) + { + var images = await petSkinImageRepository.Queryable() + .Where(i => i.SkinId == skinId && !i.IsDeleted) + .OrderBy(i => i.SortOrder) + .ToListAsync(); + + return images.Select(BuildSkinImageOutput).ToList(); + } + + private static PetSkinImageOutput BuildSkinImageOutput(PetSkinImage i) + { + return new PetSkinImageOutput + { + Id = i.Id, + SkinId = i.SkinId, + EvolutionStageId = i.EvolutionStageId, + ImageUrl = i.ImageUrl, + SortOrder = i.SortOrder, + Type = i.Type.ToString(), + CreatedBy = i.CreatedBy, + CreatedAt = i.CreatedAt, + UpdatedBy = i.UpdatedBy, + UpdatedAt = i.UpdatedAt + }; + } } diff --git a/QYZH.InteractiveMagazine.Service/PointsService.cs b/QYZH.InteractiveMagazine.Service/PointsService.cs new file mode 100644 index 0000000..dbe8d5b --- /dev/null +++ b/QYZH.InteractiveMagazine.Service/PointsService.cs @@ -0,0 +1,274 @@ +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; + +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", 400); + + 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", 400); + + 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", 400); + + // 查询用户当前积分 + var user = await Context.Queryable() + .Where(u => u.Id == input.UserId && !u.IsDeleted) + .FirstAsync(); + + if (user == null) + throw new BusinessException("用户不存在", 404); + + var previousBalance = user.Points; + var newBalance = previousBalance + input.Amount; + + // 更新用户积分 + await Context.Updateable() + .SetColumns(u => u.Points == newBalance) + .SetColumns(u => u.UpdatedAt == DateTime.Now) + .Where(u => u.Id == input.UserId && !u.IsDeleted) + .ExecuteCommandAsync(); + + // 插入积分流水记录 + 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 = PointsRecordStatusEnum.Success, + IsDeleted = false, + CreatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(), + CreatedAt = DateTime.Now, + UpdatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(), + UpdatedAt = DateTime.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", 400); + + // 查询用户当前积分 + var user = await Context.Queryable() + .Where(u => u.Id == input.UserId && !u.IsDeleted) + .FirstAsync(); + + if (user == null) + throw new BusinessException("用户不存在", 404); + + var previousBalance = user.Points; + + // 余额不足校验 + if (previousBalance < input.Amount) + throw new BusinessException($"积分不足,需要 {input.Amount} 积分,当前余额 {previousBalance}", 400); + + var newBalance = previousBalance - input.Amount; + + // 更新用户积分 + await Context.Updateable() + .SetColumns(u => u.Points == newBalance) + .SetColumns(u => u.UpdatedAt == DateTime.Now) + .Where(u => u.Id == input.UserId && !u.IsDeleted) + .ExecuteCommandAsync(); + + // 插入积分流水记录 + 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 = PointsRecordStatusEnum.Success, + IsDeleted = false, + CreatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(), + CreatedAt = DateTime.Now, + UpdatedBy = input.OperatorName ?? user.Name ?? input.UserId.ToString(), + UpdatedAt = DateTime.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("用户不存在", 404); + + // 查询累计收入(Income 类型) + var totalIncome = await Context.Queryable() + .Where(r => r.UserId == userId && !r.IsDeleted && r.Type == PointsFlowTypeEnum.Income && r.Status == 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 == 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", 400); + + if (input.PageSize <= 0 || input.PageSize > 100) + throw new BusinessException("每页条数必须在1-100之间", 400); + + 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); + + var 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 +} diff --git a/QYZH.InteractiveMagazine.Service/ProductService.cs b/QYZH.InteractiveMagazine.Service/ProductService.cs index 75dec02..77eca9c 100644 --- a/QYZH.InteractiveMagazine.Service/ProductService.cs +++ b/QYZH.InteractiveMagazine.Service/ProductService.cs @@ -3,6 +3,7 @@ using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using SqlSugar; @@ -42,7 +43,7 @@ public class ProductService(BaseRepository productRepository, ILogger

(input.Type, true), SaleStatus = input.SaleStatus, MetaData = input.MetaData, IsActive = input.IsActive, @@ -70,7 +71,7 @@ public class ProductService(BaseRepository productRepository, ILogger

productRepository, ILogger

(input.Type, true); product.SaleStatus = input.SaleStatus; product.MetaData = input.MetaData; product.IsActive = input.IsActive; @@ -139,7 +140,7 @@ public class ProductService(BaseRepository productRepository, ILogger

productRepository, ILogger

productRepository, ILogger

totalNumber = 0; var pageResult = await productRepository.Queryable() .WhereIF(!string.IsNullOrWhiteSpace(input.Name), p => p.Name.Contains(input.Name)) - .WhereIF(!string.IsNullOrWhiteSpace(input.Type), p => p.Type == input.Type) + .WhereIF(!string.IsNullOrWhiteSpace(input.Type), p => p.Type.ToString() == input.Type) .WhereIF(!string.IsNullOrWhiteSpace(input.SaleStatus), p => p.SaleStatus == input.SaleStatus) .WhereIF(input.IsActive.HasValue, p => p.IsActive == input.IsActive.Value) .OrderByDescending(p => p.CreatedAt) @@ -239,7 +240,7 @@ public class ProductService(BaseRepository productRepository, ILogger

(input.Type, true), + Status = UserJournalStatusEnum.Active, IsDeleted = false, CreatedBy = userId.ToString(), CreatedAt = DateTime.Now, @@ -112,8 +113,8 @@ public class UserJournalService( Id = userJournal.Id, UserId = userJournal.UserId, JournalId = userJournal.JournalId, - Type = userJournal.Type, - Status = userJournal.Status, + Type = userJournal.Type.ToString(), + Status = userJournal.Status.ToString(), CreatedAt = userJournal.CreatedAt }; } @@ -139,15 +140,15 @@ public class UserJournalService( RefAsync totalNumber = 0; var pageResult = await userJournalRepository.Queryable() .Where(uj => uj.UserId == userId) - .WhereIF(!string.IsNullOrWhiteSpace(input.Type), uj => uj.Type == input.Type) + .WhereIF(!string.IsNullOrWhiteSpace(input.Type), uj => uj.Type.ToString() == input.Type) .OrderByDescending(uj => uj.CreatedAt) .Select(uj => new BindJournalOutput { Id = uj.Id, UserId = uj.UserId, JournalId = uj.JournalId, - Type = uj.Type, - Status = uj.Status, + Type = uj.Type.ToString(), + Status = uj.Status.ToString(), CreatedAt = uj.CreatedAt }, true) .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); diff --git a/QYZH.InteractiveMagazine.Service/UsersService.cs b/QYZH.InteractiveMagazine.Service/UsersService.cs index 9133ccc..a4fcc7a 100644 --- a/QYZH.InteractiveMagazine.Service/UsersService.cs +++ b/QYZH.InteractiveMagazine.Service/UsersService.cs @@ -1,15 +1,27 @@ 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) : BaseRepository, IUsersService +public class UsersService( + BaseRepository usersRepository, + ILogger _logger, + IPointsService pointsService, + ICheckInService checkInService, + ICompensationTaskService compensationTaskService, + IUserJournalService userJournalService, + IOperationLogService operationLogService) : BaseRepository, IUsersService { ///

/// 分页查询用户列表 @@ -25,16 +37,134 @@ public class UsersService(BaseRepository usersRepository, ILogger - /// 获取用户详情 + /// 获取用户详情(包含积分记录、签到记录、补偿任务、期刊列表) /// - public async Task> GetDetailAsync(long id) + public async Task> GetDetailAsync(long id) { var user = await GetByIdAsync(u => u.Id == id); if (user == null) { - return BaseResponse.Fail("用户不存在"); + return BaseResponse.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.Success(new UserDetailOutput + { + BasicInfo = user, + PointsRecords = await pointsTask, + CheckInRecords = await checkInTask, + FailedCompensationTasks = await compensationTask, + Journals = await journalsTask + }); + } + + /// + /// 获取用户积分记录(最近20条) + /// + private async Task> GetPointsRecordsAsync(long userId) + { + try + { + var result = await pointsService.GetPointsRecordsAsync(new PointsRecordQueryInput + { + UserId = userId, + PageIndex = 1, + PageSize = 20 + }); + return result.Result ?? []; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "获取用户积分记录失败,UserId: {UserId}", userId); + return []; + } + } + + /// + /// 获取用户签到记录 + /// + private async Task> GetCheckInRecordsAsync(long userId) + { + try + { + var info = await checkInService.GetCheckInInfoAsync(userId); + return info.RecentRecords ?? []; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "获取用户签到记录失败,UserId: {UserId}", userId); + return []; + } + } + + /// + /// 获取用户失败的补偿任务(需要手动处理) + /// + private async Task> GetFailedCompensationTasksAsync(long userId) + { + try + { + var tasks = await compensationTaskService.GetTasksAsync(new GetCompensationTasksInput + { + Status = CompensationTaskStatusEnum.Failed, + Limit = 50 + }); + return tasks.Where(t => t.UserId == userId).ToList(); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "获取用户失败补偿任务失败,UserId: {UserId}", userId); + return []; + } + } + + /// + /// 获取用户拥有的期刊列表(含期刊详情) + /// + private async Task> GetUserJournalsWithDetailAsync(long userId) + { + try + { + var userJournals = await Context.Queryable() + .Where(uj => uj.UserId == userId && !uj.IsDeleted) + .OrderByDescending(uj => uj.CreatedAt) + .ToListAsync(); + + var result = new List(); + foreach (var uj in userJournals) + { + var journal = await Context.Queryable() + .Where(j => j.Id == uj.JournalId && !j.IsDeleted) + .FirstAsync(); + + if (journal != null) + { + 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 + }); + } + } + return result; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "获取用户期刊列表失败,UserId: {UserId}", userId); + return []; } - return BaseResponse.Success(user); } /// @@ -48,7 +178,7 @@ public class UsersService(BaseRepository usersRepository, ILogger new Users { Status = statusValue }, u => u.Id == id @@ -56,4 +186,104 @@ public class UsersService(BaseRepository usersRepository, ILogger + /// 手动增加用户积分 + /// + 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("用户不存在", 404); + + // 调用积分服务增加积分 + 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("用户不存在", 404); + + // 调用积分服务扣除积分 + 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 + }; + } } diff --git a/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs b/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs index 7731a32..43e1e07 100644 --- a/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs +++ b/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs @@ -6,6 +6,7 @@ using QYZH.InteractiveMagazine.Infrastructure.Cache; using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Models.Settings; using QYZH.InteractiveMagazine.Models.WeChat; using QYZH.InteractiveMagazine.Repository; @@ -70,8 +71,8 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura OpenId = wxResponse.OpenId, UnionId = wxResponse.UnionId, Phone = phone, - Type = "Normal", - Status = "Active", + Type = UsersTypeEnum.Normal, + Status = UserStatusEnum.Active, GrowthPoints = 0, Points = 0, IsLastOnline = true @@ -189,7 +190,7 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura throw new BusinessException("无法切换到该用户", 403); } - if (targetUser.Status == "Disabled") + if (targetUser.Status == UserStatusEnum.Disabled) { throw new BusinessException("目标账号已被禁用", 403); } @@ -345,8 +346,8 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura Phone = user.Phone, Points = user.Points, GrowthPoints = user.GrowthPoints, - Type = user.Type, - Status = user.Status, + Type = user.Type.ToString(), + Status = user.Status.ToString(), IsLastOnline = user.IsLastOnline, CreatedAt = user.CreatedAt, UpdatedAt = user.UpdatedAt, diff --git a/QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs b/QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs index 333c96d..ad21ff9 100644 --- a/QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs +++ b/QYZH.InteractiveMagazine.Service/WeChatCommunityService.cs @@ -3,6 +3,7 @@ using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using SqlSugar; @@ -79,7 +80,7 @@ public class WeChatCommunityService( UserId = m.UserId, Content = m.Content, ImageUrl = m.ImageUrl, - Type = m.Type, + Type = m.Type.ToString(), LikeCount = m.LikeCount, IsFeatured = m.IsFeatured, IsLiked = likedIds.Contains(m.Id), @@ -126,7 +127,7 @@ public class WeChatCommunityService( // 检查是否已点赞 var existingLike = await Context.Queryable() - .Where(l => l.UserId == userId && l.MessageId == input.MessageId && l.Type == "Like") + .Where(l => l.UserId == userId && l.MessageId == input.MessageId && l.Type == CommunityMessageLikeTypeEnum.Like) .FirstAsync(); if (existingLike != null) @@ -139,7 +140,7 @@ public class WeChatCommunityService( { UserId = userId, MessageId = input.MessageId, - Type = "Like", + Type = CommunityMessageLikeTypeEnum.Like, CreatedBy = "System", UpdatedBy = "System", CreatedAt = DateTime.Now, @@ -192,7 +193,7 @@ public class WeChatCommunityService( } var existingLike = await Context.Queryable() - .Where(l => l.UserId == userId && l.MessageId == messageId && l.Type == "Like") + .Where(l => l.UserId == userId && l.MessageId == messageId && l.Type == CommunityMessageLikeTypeEnum.Like) .FirstAsync(); if (existingLike == null) @@ -235,7 +236,7 @@ public class WeChatCommunityService( private async Task> GetUserJournalIds(long userId) { return await Context.Queryable() - .Where(uj => uj.UserId == userId && uj.Status == "Active") + .Where(uj => uj.UserId == userId && uj.Status == UserJournalStatusEnum.Active) .Select(uj => uj.JournalId) .ToListAsync(); } @@ -248,7 +249,7 @@ public class WeChatCommunityService( if (!messageIds.Any()) return new HashSet(); var likes = await Context.Queryable() - .Where(l => l.UserId == userId && messageIds.Contains(l.MessageId) && l.Type == "Like" && !l.IsDeleted) + .Where(l => l.UserId == userId && messageIds.Contains(l.MessageId) && l.Type == CommunityMessageLikeTypeEnum.Like && !l.IsDeleted) .Select(l => l.MessageId) .ToListAsync(); diff --git a/QYZH.InteractiveMagazine.Service/WxMallService.cs b/QYZH.InteractiveMagazine.Service/WxMallService.cs index bce4cf8..91cb27d 100644 --- a/QYZH.InteractiveMagazine.Service/WxMallService.cs +++ b/QYZH.InteractiveMagazine.Service/WxMallService.cs @@ -3,7 +3,9 @@ using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto.Bag; using QYZH.InteractiveMagazine.Models.Dto.Mall; +using QYZH.InteractiveMagazine.Models.Dto.Points; using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using System.Text.Json; @@ -15,6 +17,7 @@ namespace QYZH.InteractiveMagazine.Service; public class WxMallService( BaseRepository exchangeRecordRepository, ICheckInService checkInService, + IPointsService pointsService, ILogger logger) : BaseRepository, IWxMallService { @@ -43,14 +46,14 @@ public class WxMallService( { var query = exchangeRecordRepository.Context.Queryable() .Where(p => !p.IsDeleted && p.IsActive && p.SaleStatus == "OnSale") - .WhereIF(!string.IsNullOrEmpty(type), p => p.Type == type) + .WhereIF(!string.IsNullOrEmpty(type), p => p.Type.ToString() == type) .OrderByDescending(p => p.CreatedAt); var products = await query.ToListAsync(); // 批量提取 PetBg 商品的 SkinId var skinProductMap = products - .Where(p => p.Type == "PetBg") + .Where(p => p.Type == ProductTypeEnum.PetBg) .Select(p => new { ProductId = p.Id, SkinId = GetSkinIdFromMetaData(p.MetaData) }) .Where(x => x.SkinId > 0) .ToList(); @@ -80,7 +83,7 @@ public class WxMallService( var skinProductIds = skinProductMap.Select(x => x.ProductId).ToList(); var bagItems = skinProductIds.Count > 0 ? await exchangeRecordRepository.Context.Queryable() - .Where(b => b.UserId == userId && !b.IsDeleted && b.Status == "Available" + .Where(b => b.UserId == userId && !b.IsDeleted && b.Status == UserBagStatusEnum.Available && skinProductIds.Contains(b.ItemId)) .ToListAsync() : new List(); @@ -99,7 +102,7 @@ public class WxMallService( Description = p.Description, ImageUrl = p.ImageUrl, Price = p.Price, - Type = p.Type, + Type = p.Type.ToString(), Owned = owned, Skin = skin != null ? new PetSkinBrief { @@ -125,7 +128,7 @@ public class WxMallService( if (product == null) return null; PetSkinBrief? skinBrief = null; - if (product.Type == "PetBg") + if (product.Type == ProductTypeEnum.PetBg) { var skinId = GetSkinIdFromMetaData(product.MetaData); if (skinId > 0) @@ -155,7 +158,7 @@ public class WxMallService( } var owned = await exchangeRecordRepository.Context.Queryable() - .Where(b => b.UserId == userId && b.ItemId == product.Id && !b.IsDeleted && b.Status == "Available") + .Where(b => b.UserId == userId && b.ItemId == product.Id && !b.IsDeleted && b.Status == UserBagStatusEnum.Available) .AnyAsync(); return new WxProductOutput @@ -165,7 +168,7 @@ public class WxMallService( Description = product.Description, ImageUrl = product.ImageUrl, Price = product.Price, - Type = product.Type, + Type = product.Type.ToString(), Owned = owned, Skin = skinBrief }; @@ -195,40 +198,22 @@ public class WxMallService( var totalCost = product.Price * input.Quantity; - // 查询用户积分 - var user = await exchangeRecordRepository.Context.Queryable() - .Where(u => u.Id == userId && !u.IsDeleted) - .FirstAsync(); - - if (user == null) - throw new BusinessException("用户不存在", 404); - - if (user.Points < totalCost) - throw new BusinessException($"积分不足,需要 {totalCost} 积分,当前余额 {user.Points}", 400); - - var newPointsBalance = user.Points - totalCost; + DeductPointsOutput? pointsResult = null; long recordId = 0; await exchangeRecordRepository.UseTranAsync(async () => { - // 扣除用户积分 - await exchangeRecordRepository.Context.Updateable() - .SetColumns(u => u.Points == newPointsBalance) - .SetColumns(u => u.UpdatedAt == DateTime.Now) - .Where(u => u.Id == userId && !u.IsDeleted) - .ExecuteCommandAsync(); - // 创建兑换记录 var record = new ExchangeRecord { UserId = userId, ProductId = product.Id, ProductName = product.Name, - ProductType = product.Type, + ProductType = product.Type.ToString(), PointsCost = totalCost, - PointsBalance = newPointsBalance, + PointsBalance = 0, // 稍后赋值 Quantity = input.Quantity, - Status = "Success", + Status = ExchangeRecordStatusEnum.Success, IsDeleted = false, CreatedBy = userId.ToString(), CreatedAt = DateTime.Now, @@ -238,9 +223,25 @@ public class WxMallService( var inserted = await exchangeRecordRepository.InsertReturnEntityAsync(record); recordId = inserted.Id; + // 扣除积分 + pointsResult = await pointsService.DeductPointsInTranAsync(new DeductPointsInput + { + UserId = userId, + Amount = totalCost, + ChangeType = PointsChangeTypeEnum.Exchange, + RelatedId = recordId, + Description = $"兑换 {product.Name} x{input.Quantity}" + }); + + // 更新兑换记录的积分余额 + await exchangeRecordRepository.Updateable() + .SetColumns(r => r.PointsBalance == pointsResult.NewBalance) + .Where(r => r.Id == recordId) + .ExecuteCommandAsync(); + // 加入背包 var existingBag = await exchangeRecordRepository.Context.Queryable() - .Where(b => b.UserId == userId && b.ItemId == product.Id && !b.IsDeleted && b.Status == "Available") + .Where(b => b.UserId == userId && b.ItemId == product.Id && !b.IsDeleted && b.Status == UserBagStatusEnum.Available) .FirstAsync(); if (existingBag != null) @@ -259,11 +260,11 @@ public class WxMallService( { UserId = userId, ItemId = product.Id, - ItemType = product.Type, + ItemType = product.Type.ToString(), Quantity = input.Quantity, MetaData = product.MetaData, - Type = product.Type, - Status = "Available", + Type = Enum.TryParse(product.Type.ToString(), out var bagType) ? bagType : default, + Status = UserBagStatusEnum.Available, IsDeleted = false, CreatedBy = userId.ToString(), CreatedAt = DateTime.Now, @@ -272,25 +273,6 @@ public class WxMallService( }; await exchangeRecordRepository.Context.Insertable(bagItem).ExecuteCommandAsync(); } - - // 创建积分消耗记录 - var pointsRecord = new PointsRecord - { - UserId = userId, - ChangeAmount = -totalCost, - BalanceAfter = newPointsBalance, - ChangeType = "Exchange", - RelatedId = recordId, - Description = $"兑换 {product.Name} x{input.Quantity}", - Type = "Expense", - Status = "Success", - IsDeleted = false, - CreatedBy = userId.ToString(), - CreatedAt = DateTime.Now, - UpdatedBy = userId.ToString(), - UpdatedAt = DateTime.Now - }; - await exchangeRecordRepository.Context.Insertable(pointsRecord).ExecuteCommandAsync(); }); logger.LogInformation("兑换成功,UserId: {UserId}, Product: {Product}, Cost: {Cost}", @@ -301,7 +283,7 @@ public class WxMallService( RecordId = recordId, ProductName = product.Name, PointsCost = totalCost, - PointsBalance = newPointsBalance, + PointsBalance = pointsResult!.NewBalance, Message = $"兑换成功!{product.Name} x{input.Quantity} 已放入背包" }; } @@ -324,7 +306,7 @@ public class WxMallService( PointsCost = r.PointsCost, PointsBalance = r.PointsBalance, Quantity = r.Quantity, - Status = r.Status, + Status = r.Status.ToString(), CreatedAt = r.CreatedAt }) .ToListAsync(); @@ -336,7 +318,7 @@ public class WxMallService( public async Task> GetBagItemsAsync(long userId, string? itemType = null) { var query = exchangeRecordRepository.Context.Queryable() - .Where(b => b.UserId == userId && !b.IsDeleted && b.Status == "Available") + .Where(b => b.UserId == userId && !b.IsDeleted && b.Status == UserBagStatusEnum.Available) .WhereIF(!string.IsNullOrEmpty(itemType), b => b.ItemType == itemType) .OrderByDescending(b => b.CreatedAt); @@ -391,7 +373,7 @@ public class WxMallService( ItemId = b.ItemId, ItemType = b.ItemType, Quantity = b.Quantity, - Status = b.Status, + Status = b.Status.ToString(), CreatedAt = b.CreatedAt, Product = product != null ? new BagProductBrief { @@ -421,7 +403,7 @@ public class WxMallService( throw new BusinessException("背包物品Id无效", 400); var bagItem = await exchangeRecordRepository.Context.Queryable() - .Where(b => b.Id == input.BagItemId && b.UserId == userId && !b.IsDeleted && b.Status == "Available") + .Where(b => b.Id == input.BagItemId && b.UserId == userId && !b.IsDeleted && b.Status == UserBagStatusEnum.Available) .FirstAsync(); if (bagItem == null) @@ -462,7 +444,7 @@ public class WxMallService( if (bagItem.Quantity <= 1) { await exchangeRecordRepository.Context.Updateable() - .SetColumns(b => b.Status == "UsedUp") + .SetColumns(b => b.Status == UserBagStatusEnum.Expired) .SetColumns(b => b.Quantity == 0) .SetColumns(b => b.UpdatedAt == DateTime.Now) .Where(b => b.Id == bagItem.Id) @@ -525,7 +507,7 @@ public class WxMallService( // 校验背包中是否拥有该皮肤(通过 MetaData 中的 SkinId 判断) var hasSkin = await exchangeRecordRepository.Context.Queryable() - .Where(b => b.UserId == userId && !b.IsDeleted && b.Status == "Available" && b.Quantity > 0 + .Where(b => b.UserId == userId && !b.IsDeleted && b.Status == UserBagStatusEnum.Available && b.Quantity > 0 && b.ItemType == "PetBg") .ToListAsync(); diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/CommunityMessageController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/CommunityMessageController.cs index a3fabc4..0e6025f 100644 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/CommunityMessageController.cs +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/CommunityMessageController.cs @@ -101,7 +101,7 @@ public class CommunityMessageController : BaseController try { await _communityMessageService.FreezeAsync(id, input.Status); - return Success(new object(), input.Status == 2 ? "冻结消息成功" : "解冻消息成功"); + return Success(new object(), input.Status == (int)MessageStatusEnum.Frozen ? "冻结消息成功" : "解冻消息成功"); } catch (BusinessException ex) { @@ -160,4 +160,27 @@ public class CommunityMessageController : BaseController return BaseResponse.Fail("操作失败,请稍后重试"); } } + + /// + /// 批量发布消息 + /// + [HttpPost("batch-publish")] + public async Task> BatchPublishAsync([FromBody] AdminBatchPublishInput input) + { + try + { + await _communityMessageService.BatchPublishAsync(input.Ids); + return Success(new object(), "批量发布消息成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "批量发布社区消息业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "批量发布社区消息系统异常,IDs:{Ids}", input.Ids); + return BaseResponse.Fail("批量发布消息失败,请稍后重试"); + } + } } diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/CompensationManageController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/CompensationManageController.cs new file mode 100644 index 0000000..b323f5a --- /dev/null +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/CompensationManageController.cs @@ -0,0 +1,124 @@ +using Microsoft.AspNetCore.Mvc; +using QYZH.InteractiveMagazine.IService; +using QYZH.InteractiveMagazine.Models.Common; +using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Dto.Compensation; + +namespace QYZH.InteractiveMagazine.WebApi.Controllers; + +/// +/// 补偿任务管理控制器 +/// +[Route("api/[controller]")] +[ApiController] +public class CompensationManageController : BaseController +{ + private readonly ICompensationManageService _compensationManageService; + private readonly ILogger _logger; + + public CompensationManageController(ICompensationManageService compensationManageService, ILogger logger) + { + _compensationManageService = compensationManageService; + _logger = logger; + } + + /// + /// 分页查询补偿任务列表 + /// + [HttpPost("list")] + public async Task>> GetList([FromBody] CompensationTaskQueryInput input) + { + try + { + var result = await _compensationManageService.GetListAsync(input); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "查询补偿任务列表业务异常: {Message}", ex.Message); + return BaseResponse>.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询补偿任务列表系统异常"); + return BaseResponse>.Fail("查询补偿任务列表失败,请稍后重试"); + } + } + + /// + /// 获取补偿任务详情 + /// + [HttpGet("{id}/detail")] + public async Task> GetDetail(long id) + { + try + { + var result = await _compensationManageService.GetDetailAsync(id); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "获取补偿任务详情业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取补偿任务详情系统异常,TaskId: {TaskId}", id); + return BaseResponse.Fail("获取补偿任务详情失败,请稍后重试"); + } + } + + /// + /// 手动重试补偿任务 + /// + [HttpPost("{id}/retry")] + public async Task> Retry(long id, [FromBody] CompensationRetryInput input) + { + try + { + var operatorId = GetCurrentUserId() ?? 0; + var operatorName = GetCurrentUserName() ?? "Unknown"; + var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); + + await _compensationManageService.RetryAsync(id, operatorId, operatorName, input, ipAddress); + return Success(new object(), "补偿任务已重置为待处理状态"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "手动重试补偿任务业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "手动重试补偿任务系统异常,TaskId: {TaskId}", id); + return BaseResponse.Fail("手动重试失败,请稍后重试"); + } + } + + /// + /// 标记补偿任务为已解决 + /// + [HttpPost("{id}/resolve")] + public async Task> Resolve(long id, [FromBody] CompensationResolveInput input) + { + try + { + var operatorId = GetCurrentUserId() ?? 0; + var operatorName = GetCurrentUserName() ?? "Unknown"; + var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); + + await _compensationManageService.ResolveAsync(id, operatorId, operatorName, input, ipAddress); + return Success(new object(), "补偿任务已标记为已解决"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "标记补偿任务已解决业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "标记补偿任务已解决系统异常,TaskId: {TaskId}", id); + return BaseResponse.Fail("标记已解决失败,请稍后重试"); + } + } +} diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/OperationLogController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/OperationLogController.cs new file mode 100644 index 0000000..02854d0 --- /dev/null +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/OperationLogController.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Mvc; +using QYZH.InteractiveMagazine.IService; +using QYZH.InteractiveMagazine.Models.Common; +using QYZH.InteractiveMagazine.Models.Dto; + +namespace QYZH.InteractiveMagazine.WebApi.Controllers; + +/// +/// 操作日志控制器 +/// +[Route("api/[controller]")] +[ApiController] +public class OperationLogController : BaseController +{ + private readonly IOperationLogService _operationLogService; + private readonly ILogger _logger; + + public OperationLogController(IOperationLogService operationLogService, ILogger logger) + { + _operationLogService = operationLogService; + _logger = logger; + } + + /// + /// 分页查询操作日志 + /// + [HttpPost("list")] + public async Task>> GetList([FromBody] OperationLogQueryInput input) + { + try + { + var result = await _operationLogService.GetListAsync(input); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "查询操作日志业务异常: {Message}", ex.Message); + return BaseResponse>.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询操作日志系统异常"); + return BaseResponse>.Fail("查询操作日志失败,请稍后重试"); + } + } + + /// + /// 获取操作日志详情 + /// + [HttpGet("{id}")] + public async Task> GetDetail(long id) + { + try + { + var result = await _operationLogService.GetDetailAsync(id); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "查询操作日志详情业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询操作日志详情系统异常"); + return BaseResponse.Fail("查询操作日志详情失败,请稍后重试"); + } + } +} diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/PetManageController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/PetManageController.cs new file mode 100644 index 0000000..8d69490 --- /dev/null +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/PetManageController.cs @@ -0,0 +1,494 @@ +using Microsoft.AspNetCore.Mvc; +using QYZH.InteractiveMagazine.IService; +using QYZH.InteractiveMagazine.Models.Common; +using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Dto.Pet; +using QYZH.InteractiveMagazine.Models.Enum; + +namespace QYZH.InteractiveMagazine.WebApi.Controllers; + +/// +/// 宠物管理控制器(后台) +/// +[Route("api/[controller]")] +[ApiController] +[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))] +public class PetManageController : BaseController +{ + private readonly IPetService _petService; + private readonly ILogger _logger; + + public PetManageController(IPetService petService, ILogger logger) + { + _petService = petService; + _logger = logger; + } + + // ==================== 宠物模板管理 ==================== + + /// + /// 创建宠物模板 + /// + [HttpPost] + public async Task> CreateTemplateAsync([FromBody] PetTemplateInput input) + { + try + { + var result = await _petService.CreateTemplateAsync(input); + return Success(result, "创建宠物模板成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "创建宠物模板业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建宠物模板系统异常"); + return BaseResponse.Fail("创建宠物模板失败,请稍后重试"); + } + } + + /// + /// 更新宠物模板 + /// + [HttpPut("{id}")] + public async Task> UpdateTemplateAsync(long id, [FromBody] PetTemplateInput input) + { + try + { + var result = await _petService.UpdateTemplateAsync(id, input); + return Success(result, "更新宠物模板成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "更新宠物模板业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "更新宠物模板系统异常,ID:{Id}", id); + return BaseResponse.Fail("更新宠物模板失败,请稍后重试"); + } + } + + /// + /// 删除宠物模板 + /// + [HttpDelete("{id}")] + public async Task> DeleteTemplateAsync(long id) + { + try + { + await _petService.DeleteTemplateAsync(id); + return Success(new object(), "删除宠物模板成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "删除宠物模板业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除宠物模板系统异常,ID:{Id}", id); + return BaseResponse.Fail("删除宠物模板失败,请稍后重试"); + } + } + + /// + /// 获取单个宠物模板 + /// + [HttpGet("{id}")] + public async Task> GetTemplateByIdAsync(long id) + { + try + { + var result = await _petService.GetTemplateByIdAsync(id); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "获取宠物模板业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取宠物模板系统异常,ID:{Id}", id); + return BaseResponse.Fail("获取宠物模板失败,请稍后重试"); + } + } + + /// + /// 分页查询宠物模板列表 + /// + [HttpPost("list")] + public async Task>> GetTemplatesAsync([FromBody] PetTemplateQueryInput input) + { + try + { + var result = await _petService.GetTemplatesAsync(input); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "查询宠物模板列表业务异常: {Message}", ex.Message); + return BaseResponse>.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询宠物模板列表系统异常"); + return BaseResponse>.Fail("查询宠物模板列表失败,请稍后重试"); + } + } + + /// + /// 更新宠物模板状态(启用/禁用) + /// + [HttpPut("{id}/status")] + public async Task> UpdateTemplateStatusAsync(long id, [FromBody] string status) + { + try + { + await _petService.UpdateTemplateStatusAsync(id, status); + return Success(new object(), "更新状态成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "更新模板状态业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "更新模板状态系统异常,ID:{Id}", id); + return BaseResponse.Fail("更新模板状态失败,请稍后重试"); + } + } + + // ==================== 进化链管理 ==================== + + /// + /// 创建进化阶段 + /// + [HttpPost("evolution")] + public async Task> CreateEvolutionAsync([FromBody] PetEvolutionInput input) + { + try + { + var result = await _petService.CreateEvolutionAsync(input); + return Success(result, "创建进化阶段成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "创建进化阶段业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建进化阶段系统异常"); + return BaseResponse.Fail("创建进化阶段失败,请稍后重试"); + } + } + + /// + /// 更新进化阶段 + /// + [HttpPut("evolution/{id}")] + public async Task> UpdateEvolutionAsync(long id, [FromBody] PetEvolutionInput input) + { + try + { + var result = await _petService.UpdateEvolutionAsync(id, input); + return Success(result, "更新进化阶段成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "更新进化阶段业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "更新进化阶段系统异常,ID:{Id}", id); + return BaseResponse.Fail("更新进化阶段失败,请稍后重试"); + } + } + + /// + /// 删除进化阶段 + /// + [HttpDelete("evolution/{id}")] + public async Task> DeleteEvolutionAsync(long id) + { + try + { + await _petService.DeleteEvolutionAsync(id); + return Success(new object(), "删除进化阶段成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "删除进化阶段业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除进化阶段系统异常,ID:{Id}", id); + return BaseResponse.Fail("删除进化阶段失败,请稍后重试"); + } + } + + /// + /// 获取单个进化阶段 + /// + [HttpGet("evolution/{id}")] + public async Task> GetEvolutionByIdAsync(long id) + { + try + { + var result = await _petService.GetEvolutionByIdAsync(id); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "获取进化阶段业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取进化阶段系统异常,ID:{Id}", id); + return BaseResponse.Fail("获取进化阶段失败,请稍后重试"); + } + } + + /// + /// 分页查询进化阶段列表 + /// + [HttpPost("evolution/list")] + public async Task>> GetEvolutionsAsync([FromBody] PetEvolutionQueryInput input) + { + try + { + var result = await _petService.GetEvolutionsAsync(input); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "查询进化阶段列表业务异常: {Message}", ex.Message); + return BaseResponse>.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询进化阶段列表系统异常"); + return BaseResponse>.Fail("查询进化阶段列表失败,请稍后重试"); + } + } + + // ==================== 皮肤管理 ==================== + + /// + /// 创建皮肤 + /// + [HttpPost("skin")] + public async Task> CreateSkinAsync([FromBody] PetSkinInput input) + { + try + { + var result = await _petService.CreateSkinAsync(input); + return Success(result, "创建皮肤成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "创建皮肤业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建皮肤系统异常"); + return BaseResponse.Fail("创建皮肤失败,请稍后重试"); + } + } + + /// + /// 更新皮肤 + /// + [HttpPut("skin/{id}")] + public async Task> UpdateSkinAsync(long id, [FromBody] PetSkinInput input) + { + try + { + var result = await _petService.UpdateSkinAsync(id, input); + return Success(result, "更新皮肤成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "更新皮肤业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "更新皮肤系统异常,ID:{Id}", id); + return BaseResponse.Fail("更新皮肤失败,请稍后重试"); + } + } + + /// + /// 删除皮肤 + /// + [HttpDelete("skin/{id}")] + public async Task> DeleteSkinAsync(long id) + { + try + { + await _petService.DeleteSkinAsync(id); + return Success(new object(), "删除皮肤成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "删除皮肤业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除皮肤系统异常,ID:{Id}", id); + return BaseResponse.Fail("删除皮肤失败,请稍后重试"); + } + } + + /// + /// 获取单个皮肤(含图片列表) + /// + [HttpGet("skin/{id}")] + public async Task> GetSkinByIdAsync(long id) + { + try + { + var result = await _petService.GetSkinByIdAsync(id); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "获取皮肤业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取皮肤系统异常,ID:{Id}", id); + return BaseResponse.Fail("获取皮肤失败,请稍后重试"); + } + } + + /// + /// 分页查询皮肤列表 + /// + [HttpPost("skin/list")] + public async Task>> GetSkinsAsync([FromBody] PetSkinQueryInput input) + { + try + { + var result = await _petService.GetSkinsAsync(input); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "查询皮肤列表业务异常: {Message}", ex.Message); + return BaseResponse>.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询皮肤列表系统异常"); + return BaseResponse>.Fail("查询皮肤列表失败,请稍后重试"); + } + } + + // ==================== 皮肤图片管理 ==================== + + /// + /// 创建皮肤图片 + /// + [HttpPost("skin-image")] + public async Task> CreateSkinImageAsync([FromBody] PetSkinImageInput input) + { + try + { + var result = await _petService.CreateSkinImageAsync(input); + return Success(result, "创建皮肤图片成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "创建皮肤图片业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建皮肤图片系统异常"); + return BaseResponse.Fail("创建皮肤图片失败,请稍后重试"); + } + } + + /// + /// 更新皮肤图片 + /// + [HttpPut("skin-image/{id}")] + public async Task> UpdateSkinImageAsync(long id, [FromBody] PetSkinImageInput input) + { + try + { + var result = await _petService.UpdateSkinImageAsync(id, input); + return Success(result, "更新皮肤图片成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "更新皮肤图片业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "更新皮肤图片系统异常,ID:{Id}", id); + return BaseResponse.Fail("更新皮肤图片失败,请稍后重试"); + } + } + + /// + /// 删除皮肤图片 + /// + [HttpDelete("skin-image/{id}")] + public async Task> DeleteSkinImageAsync(long id) + { + try + { + await _petService.DeleteSkinImageAsync(id); + return Success(new object(), "删除皮肤图片成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "删除皮肤图片业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除皮肤图片系统异常,ID:{Id}", id); + return BaseResponse.Fail("删除皮肤图片失败,请稍后重试"); + } + } + + /// + /// 获取指定皮肤的所有图片 + /// + [HttpGet("skin-images/{skinId}")] + public async Task>> GetSkinImagesAsync(long skinId) + { + try + { + var result = await _petService.GetSkinImagesBySkinIdAsync(skinId); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "获取皮肤图片列表业务异常: {Message}", ex.Message); + return BaseResponse>.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取皮肤图片列表系统异常,SkinId:{SkinId}", skinId); + return BaseResponse>.Fail("获取皮肤图片列表失败,请稍后重试"); + } + } +} diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/UsersController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/UsersController.cs index 5239322..164d783 100644 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/UsersController.cs +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/UsersController.cs @@ -3,6 +3,7 @@ using QYZH.InteractiveMagazine.IService; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Dto.Points; namespace QYZH.InteractiveMagazine.WebApi.Controllers; @@ -32,10 +33,10 @@ public class UsersController : BaseController } /// - /// 获取用户详情 + /// 获取用户详情(包含积分记录、签到记录、补偿任务、期刊列表) /// [HttpGet("{id}")] - public async Task> GetDetail(long id) + public async Task> GetDetail(long id) { return await _usersService.GetDetailAsync(id); } @@ -48,4 +49,58 @@ public class UsersController : BaseController { return await _usersService.UpdateStatusAsync(id, input); } + + /// + /// 手动增加用户积分 + /// + [HttpPost("{userId}/points/add")] + public async Task> ManualAddPoints(long userId, [FromBody] ManualAddPointsInput input) + { + try + { + var operatorId = GetCurrentUserId() ?? 0; + var operatorName = GetCurrentUserName() ?? "Unknown"; + var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); + + var result = await _usersService.ManualAddPointsAsync(userId, input, operatorId, operatorName, ipAddress); + return Success(result, "手动增加积分成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "手动增加积分业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "手动增加积分系统异常,UserId: {UserId}", userId); + return BaseResponse.Fail("手动增加积分失败,请稍后重试"); + } + } + + /// + /// 手动扣除用户积分 + /// + [HttpPost("{userId}/points/deduct")] + public async Task> ManualDeductPoints(long userId, [FromBody] ManualDeductPointsInput input) + { + try + { + var operatorId = GetCurrentUserId() ?? 0; + var operatorName = GetCurrentUserName() ?? "Unknown"; + var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); + + var result = await _usersService.ManualDeductPointsAsync(userId, input, operatorId, operatorName, ipAddress); + return Success(result, "手动扣除积分成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "手动扣除积分业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "手动扣除积分系统异常,UserId: {UserId}", userId); + return BaseResponse.Fail("手动扣除积分失败,请稍后重试"); + } + } }