Files
QYZH.InteractiveMagazine/QYZH.InteractiveMagazine.Service/CompensationTaskService.cs
glz 903ccd3073 feat: 新增签到、宠物、期刊绑定、补偿任务等业务模块,优化微信登录流程
本次提交完成了多个核心业务模块的开发与优化:
1.  宠物模块:新增宠物实体、服务接口与实现,支持创建默认宠物、激活、喂养、进化以及喂养记录查询
2.  签到模块:新增签到实体、服务接口、控制器以及相关DTO,支持用户签到和签到信息查询,新增成长值奖励字段
3.  期刊绑定模块:新增用户期刊关联实体、服务接口与控制器,支持扫码绑定期刊、解绑和查询绑定列表
4.  补偿任务模块:新增补偿任务实体、服务接口与实现,用于处理业务失败后的异步重试补偿
5.  优化微信登录流程:拆分登录与快捷登录接口,支持手机号获取,新增首次登录自动创建默认宠物逻辑
6.  调整基础路由与实体状态:修改微信控制器路由前缀,更新宠物状态枚举与默认值
2026-06-04 15:54:30 +08:00

174 lines
6.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using QYZH.InteractiveMagazine.IService;
using QYZH.InteractiveMagazine.Models.Dto.Compensation;
using QYZH.InteractiveMagazine.Models.Entity;
using QYZH.InteractiveMagazine.Repository;
namespace QYZH.InteractiveMagazine.Service;
/// <summary>
/// 补偿任务服务实现(仅负责记录,处理逻辑由外部 Hangfire 项目完成)
/// </summary>
public class CompensationTaskService(
BaseRepository<CompensationTask> taskRepository,
ILogger<CompensationTaskService> logger)
: BaseRepository<CompensationTask>, ICompensationTaskService
{
/// <summary>
/// 创建补偿任务 — 记录失败操作,供后续补偿处理
/// </summary>
public async Task<long> CreateTaskAsync(CreateCompensationTaskInput input)
{
logger.LogWarning(
"创建补偿任务TaskType: {TaskType}, BusinessSource: {BusinessSource}, UserId: {UserId}, ErrorSource: {ErrorSource}, Error: {ErrorMessage}",
input.TaskType, input.BusinessSource, input.UserId, input.ErrorSource, input.ErrorMessage);
var payloadJson = input.Payload is string str ? str : JsonConvert.SerializeObject(input.Payload);
var task = new CompensationTask
{
TaskType = input.TaskType,
BusinessSource = input.BusinessSource,
BusinessId = input.BusinessId,
UserId = input.UserId,
Payload = payloadJson,
ErrorMessage = input.ErrorMessage,
ErrorSource = input.ErrorSource,
RetryCount = 0,
MaxRetries = input.MaxRetries > 0 ? input.MaxRetries : 3,
Status = CompensationTaskStatus.Pending,
ScheduledAt = DateTime.Now,
IsDeleted = false,
CreatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedBy = "System",
UpdatedAt = DateTime.Now
};
var result = await taskRepository.InsertReturnEntityAsync(task);
logger.LogInformation("补偿任务创建成功TaskId: {TaskId}", result.Id);
return result.Id;
}
/// <summary>
/// 获取待处理的补偿任务列表
/// </summary>
public async Task<List<CompensationTaskOutput>> GetPendingTasksAsync(int limit = 50)
{
var now = DateTime.Now;
var tasks = await taskRepository.Queryable()
.Where(t => (t.Status == CompensationTaskStatus.Pending || t.Status == CompensationTaskStatus.Processing)
&& !t.IsDeleted
&& (t.ScheduledAt == null || t.ScheduledAt <= now))
.OrderBy(t => t.CreatedAt)
.Take(limit)
.Select(t => new CompensationTaskOutput
{
Id = t.Id,
TaskType = t.TaskType,
BusinessSource = t.BusinessSource,
BusinessId = t.BusinessId,
UserId = t.UserId,
Payload = t.Payload,
ErrorMessage = t.ErrorMessage,
ErrorSource = t.ErrorSource,
RetryCount = t.RetryCount,
MaxRetries = t.MaxRetries,
Status = t.Status,
ProcessedAt = t.ProcessedAt,
ScheduledAt = t.ScheduledAt,
ResultMessage = t.ResultMessage,
CreatedAt = t.CreatedAt
})
.ToListAsync();
return tasks;
}
/// <summary>
/// 根据业务来源和类型查询补偿任务(用于外部项目按条件拉取)
/// </summary>
public async Task<List<CompensationTaskOutput>> GetTasksAsync(GetCompensationTasksInput input)
{
var query = taskRepository.Queryable()
.Where(t => !t.IsDeleted);
if (!string.IsNullOrEmpty(input.Status))
query = query.Where(t => t.Status == input.Status);
if (!string.IsNullOrEmpty(input.TaskType))
query = query.Where(t => t.TaskType == input.TaskType);
if (!string.IsNullOrEmpty(input.BusinessSource))
query = query.Where(t => t.BusinessSource == input.BusinessSource);
var tasks = await query
.OrderBy(t => t.CreatedAt)
.Take(input.Limit > 0 ? input.Limit : 50)
.Select(t => new CompensationTaskOutput
{
Id = t.Id,
TaskType = t.TaskType,
BusinessSource = t.BusinessSource,
BusinessId = t.BusinessId,
UserId = t.UserId,
Payload = t.Payload,
ErrorMessage = t.ErrorMessage,
ErrorSource = t.ErrorSource,
RetryCount = t.RetryCount,
MaxRetries = t.MaxRetries,
Status = t.Status,
ProcessedAt = t.ProcessedAt,
ScheduledAt = t.ScheduledAt,
ResultMessage = t.ResultMessage,
CreatedAt = t.CreatedAt
})
.ToListAsync();
return tasks;
}
/// <summary>
/// 更新补偿任务状态(供外部处理项目回调更新结果)
/// </summary>
public async Task UpdateTaskStatusAsync(long taskId, UpdateCompensationStatusInput input)
{
var update = taskRepository.Context.Updateable<CompensationTask>()
.SetColumns(t => t.Status == input.Status)
.SetColumns(t => t.ResultMessage == input.ResultMessage)
.SetColumns(t => t.ProcessedAt == DateTime.Now)
.SetColumns(t => t.UpdatedAt == DateTime.Now);
// 如果外部传入了重试相关字段,一并更新
if (input.RetryCount.HasValue)
update = update.SetColumns(t => t.RetryCount == input.RetryCount.Value);
if (input.ScheduledAt.HasValue)
update = update.SetColumns(t => t.ScheduledAt == input.ScheduledAt.Value);
await update.Where(t => t.Id == taskId && !t.IsDeleted)
.ExecuteCommandAsync();
logger.LogInformation("补偿任务状态更新TaskId: {TaskId}, Status: {Status}", taskId, input.Status);
}
/// <summary>
/// 取消补偿任务
/// </summary>
public async Task CancelTaskAsync(long taskId, string reason)
{
await taskRepository.Context.Updateable<CompensationTask>()
.SetColumns(t => t.Status == CompensationTaskStatus.Cancelled)
.SetColumns(t => t.ResultMessage == reason)
.SetColumns(t => t.UpdatedAt == DateTime.Now)
.Where(t => t.Id == taskId && !t.IsDeleted)
.ExecuteCommandAsync();
logger.LogInformation("补偿任务已取消TaskId: {TaskId}, Reason: {Reason}", taskId, reason);
}
}